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
|
@@ -2,6 +2,7 @@ module Spree
|
|
|
2
2
|
class GiftCard < Spree.base_class
|
|
3
3
|
extend DisplayMoney
|
|
4
4
|
include Spree::SingleStoreResource
|
|
5
|
+
include Spree::Metafields
|
|
5
6
|
include Spree::Security::GiftCards if defined?(Spree::Security::GiftCards)
|
|
6
7
|
|
|
7
8
|
#
|
|
@@ -14,11 +15,13 @@ module Spree
|
|
|
14
15
|
|
|
15
16
|
event :redeem do
|
|
16
17
|
transition active: :redeemed
|
|
18
|
+
transition partially_redeemed: :redeemed
|
|
17
19
|
end
|
|
18
20
|
after_transition to: :redeemed, do: :after_redeem
|
|
19
21
|
|
|
20
22
|
event :partial_redeem do
|
|
21
23
|
transition active: :partially_redeemed
|
|
24
|
+
transition partially_redeemed: :partially_redeemed
|
|
22
25
|
end
|
|
23
26
|
end
|
|
24
27
|
|
|
@@ -53,8 +56,9 @@ module Spree
|
|
|
53
56
|
#
|
|
54
57
|
# Ransack
|
|
55
58
|
#
|
|
56
|
-
self.whitelisted_ransackable_attributes = %w[code user_id]
|
|
59
|
+
self.whitelisted_ransackable_attributes = %w[code user_id state]
|
|
57
60
|
self.whitelisted_ransackable_associations = %w[users orders batch]
|
|
61
|
+
self.whitelisted_ransackable_scopes = %w[active expired redeemed partially_redeemed]
|
|
58
62
|
|
|
59
63
|
auto_strip_attributes :code
|
|
60
64
|
|
|
@@ -129,6 +133,10 @@ module Spree
|
|
|
129
133
|
end.to_s
|
|
130
134
|
end
|
|
131
135
|
|
|
136
|
+
def to_csv(_store = nil)
|
|
137
|
+
Spree::CSV::GiftCardPresenter.new(self).call
|
|
138
|
+
end
|
|
139
|
+
|
|
132
140
|
private
|
|
133
141
|
|
|
134
142
|
def generate_code
|
|
@@ -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
|
|
|
@@ -68,7 +67,7 @@ module Spree
|
|
|
68
67
|
if variant
|
|
69
68
|
update_price if price.nil?
|
|
70
69
|
self.cost_price = variant.cost_price if cost_price.nil?
|
|
71
|
-
self.currency =
|
|
70
|
+
self.currency = order.currency if currency.nil?
|
|
72
71
|
end
|
|
73
72
|
end
|
|
74
73
|
|
|
@@ -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
|