spree_core 5.1.7 → 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/helpers/spree/currency_helper.rb +21 -6
- 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 +26 -9
- 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 +6 -2
- 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/calculator.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/gateway/bogus.rb +4 -0
- data/app/models/spree/gateway/custom_payment_source_method.rb +4 -0
- data/app/models/spree/gift_card.rb +9 -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 +3 -8
- 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/payments.rb +1 -1
- data/app/models/spree/order.rb +14 -11
- 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 +5 -1
- data/app/models/spree/pages/product_details.rb +1 -0
- data/app/models/spree/pages/search_results.rb +1 -1
- data/app/models/spree/pages/shop_all.rb +5 -1
- data/app/models/spree/pages/wishlist.rb +4 -0
- data/app/models/spree/payment.rb +1 -0
- data/app/models/spree/payment_method.rb +8 -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/addresses/update.rb +8 -1
- 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/services/spree/orders/create_user_account.rb +5 -1
- data/app/services/spree/products/prepare_nested_attributes.rb +44 -0
- data/app/sorters/spree/posts/sort.rb +40 -0
- data/app/{services → validators}/spree/addresses/phone_validator.rb +1 -1
- data/app/views/spree/shared/_payment.html.erb +11 -9
- 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/common_rake.rb +5 -5
- 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 +130 -3
- metadata +280 -7
- data/app/models/action_text/rich_text_decorator.rb +0 -11
- data/app/presenters/spree/csv/product_presenter.rb +0 -63
|
@@ -47,7 +47,7 @@ module Spree
|
|
|
47
47
|
'category3',
|
|
48
48
|
].freeze
|
|
49
49
|
|
|
50
|
-
def initialize(product, variant, index = 0, properties = [], taxons = [], store = nil)
|
|
50
|
+
def initialize(product, variant, index = 0, properties = [], taxons = [], store = nil, metafields = [])
|
|
51
51
|
@product = product
|
|
52
52
|
@variant = variant
|
|
53
53
|
@index = index
|
|
@@ -55,9 +55,10 @@ module Spree
|
|
|
55
55
|
@taxons = taxons
|
|
56
56
|
@store = store || product.stores.first
|
|
57
57
|
@currency = @store.default_currency
|
|
58
|
+
@metafields = metafields
|
|
58
59
|
end
|
|
59
60
|
|
|
60
|
-
attr_accessor :product, :variant, :index, :properties, :taxons, :store, :currency
|
|
61
|
+
attr_accessor :product, :variant, :index, :properties, :taxons, :store, :currency, :metafields
|
|
61
62
|
|
|
62
63
|
##
|
|
63
64
|
# Generates an array representing a CSV row of product variant data.
|
|
@@ -71,11 +72,13 @@ module Spree
|
|
|
71
72
|
#
|
|
72
73
|
# @return [Array] An array containing the combined product and variant CSV data.
|
|
73
74
|
def call
|
|
75
|
+
total_on_hand = variant.total_on_hand
|
|
76
|
+
|
|
74
77
|
csv = [
|
|
75
78
|
product.id,
|
|
76
79
|
variant.sku,
|
|
77
80
|
index.zero? ? product.name : nil,
|
|
78
|
-
|
|
81
|
+
product.slug,
|
|
79
82
|
index.zero? ? product.status : nil,
|
|
80
83
|
index.zero? ? product.try(:vendor_name) : nil,
|
|
81
84
|
index.zero? ? product.try(:brand_name) : nil,
|
|
@@ -86,7 +89,7 @@ module Spree
|
|
|
86
89
|
index.zero? ? product.tag_list.to_s : nil,
|
|
87
90
|
index.zero? ? product.label_list.to_s : nil,
|
|
88
91
|
variant.amount_in(currency).to_f,
|
|
89
|
-
variant.
|
|
92
|
+
variant.compare_at_amount_in(currency).to_f,
|
|
90
93
|
currency,
|
|
91
94
|
variant.width,
|
|
92
95
|
variant.height,
|
|
@@ -97,7 +100,7 @@ module Spree
|
|
|
97
100
|
variant.available_on&.strftime('%Y-%m-%d %H:%M:%S'),
|
|
98
101
|
(variant.discontinue_on || product.discontinue_on)&.strftime('%Y-%m-%d %H:%M:%S'),
|
|
99
102
|
variant.track_inventory?,
|
|
100
|
-
|
|
103
|
+
total_on_hand == BigDecimal::INFINITY ? '∞' : total_on_hand,
|
|
101
104
|
variant.backorderable?,
|
|
102
105
|
variant.tax_category&.name,
|
|
103
106
|
variant.digital?,
|
|
@@ -115,6 +118,7 @@ module Spree
|
|
|
115
118
|
if index.zero?
|
|
116
119
|
csv += taxons
|
|
117
120
|
csv += properties
|
|
121
|
+
csv += metafields
|
|
118
122
|
end
|
|
119
123
|
|
|
120
124
|
csv
|
|
@@ -12,12 +12,19 @@ module Spree
|
|
|
12
12
|
default_shipping = opts.fetch(:default_shipping, false)
|
|
13
13
|
address_changes_except = opts.fetch(:address_changes_except, [])
|
|
14
14
|
create_new_address_on_update = opts.fetch(:create_new_address_on_update, false)
|
|
15
|
+
Spree::Deprecation.warn('Spree::Addresses::Update create_new_address_on_update parameter is deprecated and will be removed in Spree 6.') if create_new_address_on_update
|
|
15
16
|
|
|
16
17
|
prepare_address_params!(address, address_params)
|
|
17
18
|
address.assign_attributes(address_params)
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
address_changes = address.changes.except(*address_changes_except)
|
|
20
21
|
|
|
22
|
+
# Ignore changes that are only different in case as encrypted fields are processed by rails as downcased
|
|
23
|
+
address_changes.reject! do |attr, (old_val, new_val)|
|
|
24
|
+
old_val.to_s.casecmp?(new_val.to_s)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
address_changed = address_changes.any?
|
|
21
28
|
if !address_changed && defaults_changed?(address, default_billing, default_shipping)
|
|
22
29
|
assign_to_user_as_default(
|
|
23
30
|
user: address.user,
|
|
@@ -4,11 +4,13 @@ module Spree
|
|
|
4
4
|
prepend Spree::ServiceModule::Base
|
|
5
5
|
|
|
6
6
|
def call(order:, line_item:, line_item_created: false, options: {})
|
|
7
|
-
|
|
8
|
-
order_updater = ::Spree::OrderUpdater.new(order)
|
|
7
|
+
order_updater = order.updater
|
|
9
8
|
|
|
10
|
-
order.
|
|
11
|
-
|
|
9
|
+
order.remove_gift_card if order.gift_card.present?
|
|
10
|
+
order.payments.store_credits.checkout.destroy_all if order.payments.store_credits.checkout.any?
|
|
11
|
+
order_updater.update_item_count
|
|
12
|
+
order_updater.update_totals
|
|
13
|
+
order_updater.persist_totals
|
|
12
14
|
|
|
13
15
|
shipment = options[:shipment]
|
|
14
16
|
if shipment.present?
|
|
@@ -6,12 +6,11 @@ module Spree
|
|
|
6
6
|
def call(order:, line_item:, options: nil)
|
|
7
7
|
options ||= {}
|
|
8
8
|
ActiveRecord::Base.transaction do
|
|
9
|
-
|
|
9
|
+
order.line_items.destroy(line_item)
|
|
10
10
|
Spree::Dependencies.cart_recalculate_service.constantize.new.call(order: order,
|
|
11
11
|
line_item: line_item,
|
|
12
12
|
options: options)
|
|
13
13
|
end
|
|
14
|
-
order.reload
|
|
15
14
|
success(line_item)
|
|
16
15
|
end
|
|
17
16
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Imports
|
|
3
|
+
module RowProcessors
|
|
4
|
+
class Base
|
|
5
|
+
def initialize(row)
|
|
6
|
+
@row = row
|
|
7
|
+
@import = row.import
|
|
8
|
+
@attributes = row.to_schema_hash
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
attr_reader :row, :import, :attributes
|
|
12
|
+
|
|
13
|
+
def process!
|
|
14
|
+
raise NotImplementedError, 'Subclasses must implement the process! method'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Imports
|
|
3
|
+
module RowProcessors
|
|
4
|
+
class ProductVariant < Base
|
|
5
|
+
OPTION_TYPES_COUNT = 3
|
|
6
|
+
|
|
7
|
+
def initialize(row)
|
|
8
|
+
super
|
|
9
|
+
@store = row.store
|
|
10
|
+
@product = ensure_product_exists
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :product, :store
|
|
14
|
+
|
|
15
|
+
def process!
|
|
16
|
+
variant_scope = options.empty? ? product.variants_including_master : product.variants
|
|
17
|
+
|
|
18
|
+
variant = if attributes['sku'].present?
|
|
19
|
+
variant_scope.where(
|
|
20
|
+
Spree::Variant.arel_table[:sku].lower.eq(attributes['sku'].strip.downcase)
|
|
21
|
+
).first || product.variants.new
|
|
22
|
+
elsif options.empty?
|
|
23
|
+
product.master
|
|
24
|
+
else
|
|
25
|
+
product.variants.new
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
variant.sku = attributes['sku'] if attributes['sku'].present?
|
|
29
|
+
variant.cost_price = attributes['cost_price'] if attributes['cost_price'].present?
|
|
30
|
+
variant.weight = attributes['weight'] if attributes['weight'].present?
|
|
31
|
+
variant.height = attributes['height'] if attributes['height'].present?
|
|
32
|
+
variant.width = attributes['width'] if attributes['width'].present?
|
|
33
|
+
variant.depth = attributes['depth'] if attributes['depth'].present?
|
|
34
|
+
variant.track_inventory = attributes['track_inventory'] if attributes['track_inventory'].present?
|
|
35
|
+
variant.option_value_variants = prepare_option_value_variants
|
|
36
|
+
variant.save!
|
|
37
|
+
|
|
38
|
+
if attributes['price'].present?
|
|
39
|
+
currency = (attributes['currency'].presence || store.default_currency).to_s.strip.upcase
|
|
40
|
+
variant.set_price(currency, attributes['price'], attributes['compare_at_price'])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if attributes['inventory_count'].present?
|
|
44
|
+
variant.set_stock(attributes['inventory_count'].to_i, attributes['inventory_backorderable']&.to_b, store.default_stock_location)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
handle_images(variant)
|
|
48
|
+
|
|
49
|
+
variant
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def ensure_product_exists
|
|
55
|
+
product = Spree::Product.new
|
|
56
|
+
if attributes['slug'].present?
|
|
57
|
+
existing_product = product_scope.find_by(slug: attributes['slug'].strip.downcase)
|
|
58
|
+
product = existing_product if existing_product.present?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
product = assign_attributes_to_product(product)
|
|
62
|
+
product.save!
|
|
63
|
+
handle_metafields(product)
|
|
64
|
+
product
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def product_scope
|
|
68
|
+
Spree::Product.accessible_by(import.current_ability, :manage)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def assign_attributes_to_product(product)
|
|
72
|
+
# setting SKU for master variant so it will be picked up in process! and won't try to create a non-master variant
|
|
73
|
+
if product.new_record?
|
|
74
|
+
product.slug = attributes['slug']
|
|
75
|
+
product.sku = attributes['sku'] if attributes['sku'].present? && options.empty?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
product.stores << store if product.stores.exclude?(store)
|
|
79
|
+
product.name = attributes['name'] if attributes['name'].present?
|
|
80
|
+
product.description = attributes['description'] if attributes['description'].present?
|
|
81
|
+
product.meta_title = attributes['meta_title'] if attributes['meta_title'].present?
|
|
82
|
+
product.meta_description = attributes['meta_description'] if attributes['meta_description'].present?
|
|
83
|
+
product.meta_keywords = attributes['meta_keywords'] if attributes['meta_keywords'].present?
|
|
84
|
+
product.status = to_spree_status(attributes['status']) if attributes['status'].present?
|
|
85
|
+
product.tag_list = attributes['tags'] if attributes['tags'].present?
|
|
86
|
+
|
|
87
|
+
product.taxons = prepare_taxons if options.empty?
|
|
88
|
+
product
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def prepare_taxons
|
|
92
|
+
taxon_pretty_names = [
|
|
93
|
+
attributes['category1'],
|
|
94
|
+
attributes['category2'],
|
|
95
|
+
attributes['category3']
|
|
96
|
+
].compact_blank.map(&:strip).uniq
|
|
97
|
+
|
|
98
|
+
return [] if taxon_pretty_names.empty?
|
|
99
|
+
|
|
100
|
+
taxons = taxon_pretty_names.map { |taxon_pretty_name| handle_taxon_line(taxon_pretty_name) }
|
|
101
|
+
taxons.compact
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def handle_taxon_line(taxon_pretty_name)
|
|
105
|
+
taxon_names = taxon_pretty_name.strip.split('->').map(&:strip).map(&:presence).compact
|
|
106
|
+
return if taxon_names.empty?
|
|
107
|
+
|
|
108
|
+
taxonomy_name = taxon_names.shift
|
|
109
|
+
taxonomy = store.taxonomies.with_matching_name(taxonomy_name).first || store.taxonomies.create!(name: taxonomy_name)
|
|
110
|
+
|
|
111
|
+
last_taxon = taxonomy.root
|
|
112
|
+
|
|
113
|
+
taxon_names.each do |taxon_name|
|
|
114
|
+
last_taxon = taxonomy.taxons.with_matching_name(taxon_name).where(parent: last_taxon).first || taxonomy.taxons.create!(name: taxon_name, parent: last_taxon)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
last_taxon
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def prepare_option_value_variants
|
|
121
|
+
return [] if options.empty?
|
|
122
|
+
|
|
123
|
+
options.map do |option|
|
|
124
|
+
option_type = Spree::OptionType.search_by_name(option[:option_name]).first || Spree::OptionType.create!(presentation: option[:option_name])
|
|
125
|
+
option_value = option_type.option_values.search_by_name(option[:option_value]).first || option_type.option_values.create!(presentation: option[:option_value])
|
|
126
|
+
|
|
127
|
+
# ensure product option types include new option type
|
|
128
|
+
Spree::ProductOptionType.find_or_create_by!(product: product, option_type: option_type)
|
|
129
|
+
|
|
130
|
+
Spree::OptionValueVariant.new(option_value: option_value)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def handle_images(variant)
|
|
135
|
+
image_urls = [
|
|
136
|
+
attributes['image1_src'],
|
|
137
|
+
attributes['image2_src'],
|
|
138
|
+
attributes['image3_src'],
|
|
139
|
+
].compact.map(&:strip).compact_blank.uniq
|
|
140
|
+
|
|
141
|
+
return if image_urls.empty?
|
|
142
|
+
|
|
143
|
+
image_urls.each do |image_url|
|
|
144
|
+
Spree::Images::SaveFromUrlJob.perform_later(variant.id, 'Spree::Variant', image_url)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def options
|
|
149
|
+
@options ||= begin
|
|
150
|
+
options = []
|
|
151
|
+
|
|
152
|
+
OPTION_TYPES_COUNT.times.map do |index|
|
|
153
|
+
next if attributes["option#{index + 1}_name"].blank?
|
|
154
|
+
next if attributes["option#{index + 1}_value"].blank?
|
|
155
|
+
|
|
156
|
+
options << {
|
|
157
|
+
index: index + 1,
|
|
158
|
+
option_name: attributes["option#{index + 1}_name"],
|
|
159
|
+
option_value: attributes["option#{index + 1}_value"]
|
|
160
|
+
}
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
options
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def handle_metafields(product)
|
|
168
|
+
return unless product.class.included_modules.include?(Spree::Metafields)
|
|
169
|
+
|
|
170
|
+
metafield_attributes = attributes.select { |key, _value| key.to_s.start_with?('metafield.') }
|
|
171
|
+
return if metafield_attributes.empty?
|
|
172
|
+
|
|
173
|
+
# Build nested attributes for metafields
|
|
174
|
+
nested_attrs = []
|
|
175
|
+
|
|
176
|
+
metafield_attributes.each do |attribute_key, value|
|
|
177
|
+
# Extract namespace.key from "metafield.namespace.key"
|
|
178
|
+
full_key = attribute_key.to_s.sub(/^metafield\./, '')
|
|
179
|
+
namespace, key = product.extract_namespace_and_key(full_key)
|
|
180
|
+
|
|
181
|
+
# Find or initialize metafield definition
|
|
182
|
+
metafield_definition = Spree::MetafieldDefinition.find_by(
|
|
183
|
+
namespace: namespace,
|
|
184
|
+
key: key,
|
|
185
|
+
resource_type: product.class.name
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
next unless metafield_definition
|
|
189
|
+
|
|
190
|
+
# Find existing metafield if product is persisted
|
|
191
|
+
existing_metafield = product.persisted? ? product.metafields.find_by(metafield_definition: metafield_definition) : nil
|
|
192
|
+
|
|
193
|
+
# Skip blank values for new metafields
|
|
194
|
+
next if value.blank? && existing_metafield.nil?
|
|
195
|
+
|
|
196
|
+
# For existing metafields with blank values, we'll mark them for destruction
|
|
197
|
+
# For new metafields, we skip them (handled above)
|
|
198
|
+
# For existing or new metafields with values, we create/update them
|
|
199
|
+
metafield_attrs = {
|
|
200
|
+
metafield_definition_id: metafield_definition.id,
|
|
201
|
+
value: value.to_s.strip,
|
|
202
|
+
type: metafield_definition.metafield_type
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
metafield_attrs[:id] = existing_metafield.id if existing_metafield
|
|
206
|
+
|
|
207
|
+
nested_attrs << metafield_attrs
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
product.update(metafields_attributes: nested_attrs) unless nested_attrs.empty?
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def to_spree_status(status)
|
|
214
|
+
case status.strip.downcase
|
|
215
|
+
when 'active'
|
|
216
|
+
'active'
|
|
217
|
+
when 'draft'
|
|
218
|
+
'draft'
|
|
219
|
+
when 'archived'
|
|
220
|
+
'archived'
|
|
221
|
+
else
|
|
222
|
+
'draft'
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Newsletter
|
|
3
|
+
class Subscribe
|
|
4
|
+
def initialize(email:, current_user: nil)
|
|
5
|
+
@email = email
|
|
6
|
+
@current_user = current_user
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
return existed_subscription if existed_subscription.present?
|
|
11
|
+
|
|
12
|
+
ActiveRecord::Base.transaction do
|
|
13
|
+
upsert_subscriber
|
|
14
|
+
return subscriber if subscriber.errors.any?
|
|
15
|
+
|
|
16
|
+
if subscriber.email == current_user&.email
|
|
17
|
+
# no need to verified since user email is already verified
|
|
18
|
+
Spree::Newsletter::Verify.new(subscriber: subscriber).call
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# deliver confirmation email after the transaction is completed
|
|
23
|
+
subscriber.deliver_newsletter_email_verification unless subscriber.verified?
|
|
24
|
+
subscriber
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
attr_reader :email, :current_user
|
|
30
|
+
|
|
31
|
+
def upsert_subscriber
|
|
32
|
+
@upsert_subscriber ||= Spree::NewsletterSubscriber.find_or_create_by(email: email) do |new_record|
|
|
33
|
+
new_record.user = Spree.user_class.find_by(email: new_record.email) || current_user
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
alias_method :subscriber, :upsert_subscriber
|
|
37
|
+
|
|
38
|
+
def existed_subscription
|
|
39
|
+
@existed_subscription ||= Spree::NewsletterSubscriber.verified.find_by(email: email)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Newsletter
|
|
3
|
+
class Verify
|
|
4
|
+
def initialize(subscriber:)
|
|
5
|
+
@subscriber = subscriber
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
verify_subscriber
|
|
10
|
+
set_user_email_marketing_to_true
|
|
11
|
+
|
|
12
|
+
subscriber
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
attr_reader :subscriber
|
|
18
|
+
|
|
19
|
+
def verify_subscriber
|
|
20
|
+
subscriber.update!(verified_at: Time.current, verification_token: nil)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def set_user_email_marketing_to_true
|
|
24
|
+
return if subscriber.user.blank?
|
|
25
|
+
|
|
26
|
+
subscriber.user.update!(accepts_email_marketing: true)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -7,7 +7,11 @@ module Spree
|
|
|
7
7
|
|
|
8
8
|
def call(order:, accepts_email_marketing: false)
|
|
9
9
|
existing_user = Spree.user_class.find_by(email: order.email)
|
|
10
|
-
|
|
10
|
+
if existing_user.present?
|
|
11
|
+
order.update_columns(user_id: existing_user.id, updated_at: Time.current)
|
|
12
|
+
order.user = existing_user
|
|
13
|
+
return success(existing_user)
|
|
14
|
+
end
|
|
11
15
|
|
|
12
16
|
user = create_new_user(order, accepts_email_marketing)
|
|
13
17
|
return failure(:user_creation_failed) unless user.persisted?
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Products
|
|
3
|
+
# Prepares nested attributes for product updates, handling multi-store scenarios
|
|
4
|
+
# and permissions.
|
|
5
|
+
#
|
|
6
|
+
# This service ensures that when editing a product in one store, taxon associations
|
|
7
|
+
# from other stores are preserved. This prevents accidental data loss when a store
|
|
8
|
+
# admin updates product categories in their store without affecting other stores.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# service = Spree::Products::PrepareNestedAttributes.new(
|
|
12
|
+
# product,
|
|
13
|
+
# current_store,
|
|
14
|
+
# params,
|
|
15
|
+
# current_ability
|
|
16
|
+
# )
|
|
17
|
+
# prepared_params = service.call
|
|
18
|
+
#
|
|
3
19
|
class PrepareNestedAttributes
|
|
4
20
|
def initialize(product, store, params, ability)
|
|
5
21
|
@product = product
|
|
@@ -60,6 +76,12 @@ module Spree
|
|
|
60
76
|
# ensure there is at least one store
|
|
61
77
|
params[:store_ids] = [store.id] if params[:store_ids].blank?
|
|
62
78
|
|
|
79
|
+
# Preserve taxon associations from other stores
|
|
80
|
+
# Only merge taxon_ids from other stores if taxon_ids are being updated
|
|
81
|
+
if params.key?(:taxon_ids)
|
|
82
|
+
params[:taxon_ids] = merge_taxons_from_other_stores(params[:taxon_ids])
|
|
83
|
+
end
|
|
84
|
+
|
|
63
85
|
# Add empty list for option_type_ids and mark variants as removed if there are no variants and options
|
|
64
86
|
if params[:variants_attributes].blank? && variants_to_remove.any? && !params.key?(:option_type_ids)
|
|
65
87
|
params[:option_type_ids] = []
|
|
@@ -133,6 +155,28 @@ module Spree
|
|
|
133
155
|
attributes
|
|
134
156
|
end
|
|
135
157
|
|
|
158
|
+
# Merges taxon IDs from other stores with submitted taxon IDs from current store.
|
|
159
|
+
#
|
|
160
|
+
# This prevents the loss of taxon associations from other stores when a product
|
|
161
|
+
# is edited in one store. Each store's taxonomy is independent, so editing
|
|
162
|
+
# categories in Store A should not affect categories in Store B.
|
|
163
|
+
#
|
|
164
|
+
# @param submitted_taxon_ids [Array<String>] Taxon IDs from the current store
|
|
165
|
+
# @return [Array<String>] Combined unique taxon IDs
|
|
166
|
+
def merge_taxons_from_other_stores(submitted_taxon_ids)
|
|
167
|
+
return submitted_taxon_ids if product.new_record?
|
|
168
|
+
|
|
169
|
+
# Get taxon IDs from other stores that should be preserved
|
|
170
|
+
other_stores_taxon_ids = product.taxons
|
|
171
|
+
.joins(:taxonomy)
|
|
172
|
+
.where.not(spree_taxonomies: { store_id: store.id })
|
|
173
|
+
.pluck(:id)
|
|
174
|
+
.map(&:to_s)
|
|
175
|
+
|
|
176
|
+
# Merge with submitted taxon IDs from current store and remove duplicates
|
|
177
|
+
(submitted_taxon_ids + other_stores_taxon_ids).uniq
|
|
178
|
+
end
|
|
179
|
+
|
|
136
180
|
def update_option_value_variants(option_value_params, existing_variant)
|
|
137
181
|
return {} unless option_value_params.present?
|
|
138
182
|
return {} unless can_manage_option_types?
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Posts
|
|
3
|
+
class Sort < ::Spree::BaseSorter
|
|
4
|
+
def initialize(scope, params = {}, allowed_sort_attributes = [])
|
|
5
|
+
super(scope, params, allowed_sort_attributes)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
posts = by_param_attributes(scope)
|
|
10
|
+
posts = select_translatable_fields(posts) if Spree.use_translations?
|
|
11
|
+
|
|
12
|
+
posts.distinct
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
attr_reader :sort, :scope, :allowed_sort_attributes
|
|
18
|
+
|
|
19
|
+
# Add translatable fields to SELECT statement to avoid InvalidColumnReference error (workaround for Mobility issue #596)
|
|
20
|
+
def select_translatable_fields(scope)
|
|
21
|
+
translatable_fields = translatable_sortable_fields
|
|
22
|
+
return scope if translatable_fields.empty?
|
|
23
|
+
|
|
24
|
+
scope.i18n.select("#{Post.table_name}.*").select(*translatable_fields)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def translatable_sortable_fields
|
|
28
|
+
fields = []
|
|
29
|
+
Post.translatable_fields.each do |field|
|
|
30
|
+
fields << field if sort_by?(field.to_s)
|
|
31
|
+
end
|
|
32
|
+
fields
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def sort_by?(field)
|
|
36
|
+
sort.detect { |s| s[0] == field }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -8,7 +8,7 @@ module Spree
|
|
|
8
8
|
module Addresses
|
|
9
9
|
class PhoneValidator < ActiveModel::Validator
|
|
10
10
|
def validate(address)
|
|
11
|
-
return if !
|
|
11
|
+
return if !address.require_phone? || address.phone.blank? || address.country.blank? || address.country_iso.blank?
|
|
12
12
|
|
|
13
13
|
phone = Phonelib.parse(address.phone)
|
|
14
14
|
unless phone.valid_for_country?(address.country_iso)
|
|
@@ -13,14 +13,16 @@
|
|
|
13
13
|
</div>
|
|
14
14
|
<% elsif source.is_a?(Spree::StoreCredit) %>
|
|
15
15
|
<% if source.originator_type == 'Spree::GiftCard' %>
|
|
16
|
-
|
|
17
|
-
<%=
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
<% if respond_to?(:heroicon) %>
|
|
17
|
+
<%= heroicon('gift', options: { style: 'margin-right: 0 !important;' }) %>
|
|
18
|
+
<% else %>
|
|
19
|
+
<%= icon('gift', height: 30) %>
|
|
20
|
+
<% end %>
|
|
21
|
+
<div>
|
|
22
|
+
<p class="mb-1"><%= Spree.t(:gift_card) %>: <strong><%= source.originator.code.upcase %></strong></p>
|
|
23
|
+
<%= @order.display_gift_card_total %>
|
|
22
24
|
</div>
|
|
23
|
-
<% elsif payment_method_icon_tag(payment.payment_method.payment_icon_name).present? %>
|
|
25
|
+
<% elsif payment.payment_method.present? && payment_method_icon_tag(payment.payment_method.payment_icon_name).present? %>
|
|
24
26
|
<div class="d-flex flex align-items-center items-center">
|
|
25
27
|
<div><%= payment_method_icon_tag(payment.payment_method.payment_icon_name) %></div>
|
|
26
28
|
<div class="ml-3"><%= Spree.t(:store_credit_name) %></div>
|
|
@@ -31,7 +33,7 @@
|
|
|
31
33
|
<%= payment_method_icon_tag source.class.to_s.demodulize.downcase, height: 30 %>
|
|
32
34
|
</div>
|
|
33
35
|
<div>
|
|
34
|
-
<% if payment.payment_method.respond_to?(:source_partial_name) %>
|
|
36
|
+
<% if payment.payment_method.present? && payment.payment_method.respond_to?(:source_partial_name) %>
|
|
35
37
|
<%= render partial: "spree/payment_sources/#{payment.payment_method.source_partial_name}", locals: { payment: payment, source: source } %>
|
|
36
38
|
<% elsif source.respond_to?(:name) %>
|
|
37
39
|
<%= source.name %>
|
|
@@ -39,7 +41,7 @@
|
|
|
39
41
|
<%= source.class.to_s.demodulize.downcase %>
|
|
40
42
|
<% end %>
|
|
41
43
|
</div>
|
|
42
|
-
<%
|
|
44
|
+
<% elsif payment.payment_method.present? %>
|
|
43
45
|
<div class="flex-shrink-0">
|
|
44
46
|
<%= payment_method_icon_tag payment.payment_method.payment_icon_name %>
|
|
45
47
|
</div>
|