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