workarea-registries 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/Gemfile +9 -0
- data/LICENSE +21 -0
- data/README.md +1 -0
- data/Rakefile +58 -0
- data/app/assets/images/workarea/storefront/registries/icons/email.svg +4 -0
- data/app/assets/images/workarea/storefront/registries/icons/link.svg +1 -0
- data/app/assets/javascripts/workarea/storefront/registries/modules/add_to_registry_button.js +49 -0
- data/app/assets/javascripts/workarea/storefront/registries/modules/copy_registry_url_button.js +51 -0
- data/app/assets/javascripts/workarea/storefront/registries/modules/toggle_registry_address.js +58 -0
- data/app/assets/javascripts/workarea/storefront/registries/templates/add_to_registry_button.jst.ejs +3 -0
- data/app/controllers/workarea/admin/registries_controller.rb +28 -0
- data/app/controllers/workarea/storefront/cart_items_controller.decorator +14 -0
- data/app/controllers/workarea/storefront/registries_controller.rb +47 -0
- data/app/controllers/workarea/storefront/users/registries_controller.rb +94 -0
- data/app/controllers/workarea/storefront/users/registry_items_controller.rb +67 -0
- data/app/controllers/workarea/storefront/users/registry_shares_controller.rb +43 -0
- data/app/controllers/workarea/storefront/users/registry_thanks_controller.rb +41 -0
- data/app/helpers/workarea/registries_helper.rb +41 -0
- data/app/mailers/workarea/storefront/registries_mailer.rb +52 -0
- data/app/models/workarea/checkout/steps/addresses.decorator +28 -0
- data/app/models/workarea/order.decorator +7 -0
- data/app/models/workarea/order/item.decorator +11 -0
- data/app/models/workarea/registry.rb +129 -0
- data/app/models/workarea/registry/item.rb +39 -0
- data/app/models/workarea/registry/public_search.rb +23 -0
- data/app/models/workarea/registry/share.rb +46 -0
- data/app/models/workarea/registry/thank.rb +26 -0
- data/app/models/workarea/search/admin/registry.rb +29 -0
- data/app/queries/workarea/search/admin_orders.decorator +7 -0
- data/app/queries/workarea/search/admin_registries.rb +16 -0
- data/app/seeds/workarea/registry_content_seeds.rb +50 -0
- data/app/services/workarea/inventory_adjustment.decorator +43 -0
- data/app/view_models/workarea/admin/registry_view_model.rb +19 -0
- data/app/view_models/workarea/storefront/checkout/addresses_view_model.decorator +17 -0
- data/app/view_models/workarea/storefront/order_item_view_model.decorator +9 -0
- data/app/view_models/workarea/storefront/order_view_model.decorator +8 -0
- data/app/view_models/workarea/storefront/registry_email_view_model.rb +37 -0
- data/app/view_models/workarea/storefront/registry_item_view_model.rb +72 -0
- data/app/view_models/workarea/storefront/registry_share_view_model.rb +6 -0
- data/app/view_models/workarea/storefront/registry_thank_view_model.rb +6 -0
- data/app/view_models/workarea/storefront/registry_view_model.rb +74 -0
- data/app/view_models/workarea/storefront/user_view_model.decorator +12 -0
- data/app/views/workarea/admin/registries/_menu.html.haml +1 -0
- data/app/views/workarea/admin/registries/index.html.haml +70 -0
- data/app/views/workarea/storefront/carts/_registry_details.html.haml +3 -0
- data/app/views/workarea/storefront/checkouts/_registry_address.html.haml +13 -0
- data/app/views/workarea/storefront/checkouts/_registry_address_fields.html.haml +28 -0
- data/app/views/workarea/storefront/checkouts/_registry_details.html.haml +3 -0
- data/app/views/workarea/storefront/products/_add_to_registry_placeholder.html.haml +1 -0
- data/app/views/workarea/storefront/registries/_registries_link.html.haml +4 -0
- data/app/views/workarea/storefront/registries/index.html.haml +39 -0
- data/app/views/workarea/storefront/registries/search.html.haml +35 -0
- data/app/views/workarea/storefront/registries/show.html.haml +63 -0
- data/app/views/workarea/storefront/registries_mailer/creation.html.haml +13 -0
- data/app/views/workarea/storefront/registries_mailer/gift_receipt.html.haml +51 -0
- data/app/views/workarea/storefront/registries_mailer/share.html.haml +21 -0
- data/app/views/workarea/storefront/registries_mailer/thank.html.haml +19 -0
- data/app/views/workarea/storefront/users/_registry_count.json.jbuilder +3 -0
- data/app/views/workarea/storefront/users/accounts/_registries.html.haml +14 -0
- data/app/views/workarea/storefront/users/registries/_form.html.haml +56 -0
- data/app/views/workarea/storefront/users/registries/_radio_list.html.haml +8 -0
- data/app/views/workarea/storefront/users/registries/_summary.html.haml +12 -0
- data/app/views/workarea/storefront/users/registries/edit.html.haml +4 -0
- data/app/views/workarea/storefront/users/registries/index.html.haml +24 -0
- data/app/views/workarea/storefront/users/registries/new.html.haml +4 -0
- data/app/views/workarea/storefront/users/registries/show.html.haml +75 -0
- data/app/views/workarea/storefront/users/registry_items/new.html.haml +10 -0
- data/app/views/workarea/storefront/users/registry_items/templates/_purchased.html.haml +29 -0
- data/app/views/workarea/storefront/users/registry_items/templates/_unpurchased.html.haml +35 -0
- data/app/views/workarea/storefront/users/registry_shares/_share_buttons.html.haml +18 -0
- data/app/views/workarea/storefront/users/registry_shares/new.html.haml +49 -0
- data/app/views/workarea/storefront/users/registry_thanks/new.html.haml +47 -0
- data/app/workers/workarea/send_gift_receipt.rb +15 -0
- data/app/workers/workarea/send_registry_creation_email.rb +12 -0
- data/app/workers/workarea/update_registry_items.rb +24 -0
- data/bin/rails +20 -0
- data/config/initializers/appends.rb +54 -0
- data/config/initializers/fields.rb +19 -0
- data/config/locales/en.yml +133 -0
- data/config/routes.rb +26 -0
- data/lib/workarea/mailer_previews/storefront/registry_mailer_preview.rb +25 -0
- data/lib/workarea/registries.rb +11 -0
- data/lib/workarea/registries/engine.rb +17 -0
- data/lib/workarea/registries/version.rb +5 -0
- data/test/dummy/config/application.rb +27 -0
- data/test/factories/workarea/registry.rb +67 -0
- data/test/integration/workarea/storefront/cart_item_registry_integration_test.rb +104 -0
- data/test/integration/workarea/storefront/checkout_registry_integration_test.rb +156 -0
- data/test/integration/workarea/storefront/registry_privacy_integration_test.rb +48 -0
- data/test/integration/workarea/storefront/user_registries_integration_test.rb +50 -0
- data/test/integration/workarea/storefront/user_registry_items_integration_test.rb +106 -0
- data/test/models/workarea/registry/item_test.rb +22 -0
- data/test/models/workarea/registry/public_search_test.rb +48 -0
- data/test/models/workarea/registry_test.rb +113 -0
- data/test/system/workarea/admin/registry_system_test.rb +20 -0
- data/test/workers/update_registry_items_test.rb +45 -0
- data/workarea-registries.gemspec +20 -0
- metadata +156 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
module Workarea
|
2
|
+
class Registry::PublicSearch
|
3
|
+
attr_reader :query
|
4
|
+
|
5
|
+
def initialize(query)
|
6
|
+
@query = query
|
7
|
+
end
|
8
|
+
|
9
|
+
def results
|
10
|
+
regex = /^#{Regexp.quote(query)}/i
|
11
|
+
criteria = [
|
12
|
+
{ name: regex },
|
13
|
+
{ id: query }, # we don't want partial matching on id
|
14
|
+
{ email: query } # we don't want partial matching on email
|
15
|
+
]
|
16
|
+
|
17
|
+
Workarea::Registry.where(
|
18
|
+
privacy: 'public',
|
19
|
+
'$or' => criteria
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Workarea
|
2
|
+
class Registry
|
3
|
+
class Share
|
4
|
+
include ApplicationDocument
|
5
|
+
|
6
|
+
field :to_name, type: String
|
7
|
+
field :to_email, type: String
|
8
|
+
field :from_name, type: String
|
9
|
+
field :from_email, type: String
|
10
|
+
field :message, type: String
|
11
|
+
field :registry_name, type: String
|
12
|
+
field :url, type: String
|
13
|
+
field :registry_id, type: String
|
14
|
+
|
15
|
+
validates :to_email, presence: true, email: true
|
16
|
+
validates :from_email, presence: true, email: true
|
17
|
+
validates :message, length: {
|
18
|
+
maximum: :max_email_share_message_length
|
19
|
+
}
|
20
|
+
validates :url, presence: true
|
21
|
+
|
22
|
+
after_initialize :sanitize_url
|
23
|
+
before_validation :sanitize_url
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def max_email_share_message_length
|
28
|
+
Workarea.config.max_email_registry_share_message_length
|
29
|
+
end
|
30
|
+
|
31
|
+
def uri
|
32
|
+
URI.parse(url)
|
33
|
+
end
|
34
|
+
|
35
|
+
def sanitize_url
|
36
|
+
return unless url.present?
|
37
|
+
self.url =
|
38
|
+
if uri.query.present?
|
39
|
+
[uri.path, uri.query].join('?')
|
40
|
+
else
|
41
|
+
uri.path
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Workarea
|
2
|
+
class Registry
|
3
|
+
class Thank
|
4
|
+
include ApplicationDocument
|
5
|
+
|
6
|
+
field :to_name, type: String
|
7
|
+
field :to_email, type: String
|
8
|
+
field :from_name, type: String
|
9
|
+
field :from_email, type: String
|
10
|
+
field :message, type: String
|
11
|
+
field :registry_id, type: String
|
12
|
+
|
13
|
+
validates :to_email, presence: true, email: true
|
14
|
+
validates :from_email, presence: true, email: true
|
15
|
+
validates :message, length: {
|
16
|
+
maximum: :max_email_thank_message_length
|
17
|
+
}
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def max_email_thank_message_length
|
22
|
+
Workarea.config.max_email_registry_thanks_message_length
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Search
|
3
|
+
class Admin
|
4
|
+
class Registry < Search::Admin
|
5
|
+
def type
|
6
|
+
'registry'
|
7
|
+
end
|
8
|
+
|
9
|
+
def jump_to_text
|
10
|
+
model.name
|
11
|
+
end
|
12
|
+
|
13
|
+
def jump_to_position
|
14
|
+
3
|
15
|
+
end
|
16
|
+
|
17
|
+
def search_text
|
18
|
+
[
|
19
|
+
'registry',
|
20
|
+
model.id,
|
21
|
+
model.name,
|
22
|
+
*model.items.map(&:product_id),
|
23
|
+
*model.items.map(&:sku)
|
24
|
+
].join(' ')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Search
|
3
|
+
class AdminRegistries
|
4
|
+
include Query
|
5
|
+
include AdminIndexSearch
|
6
|
+
include AdminSorting
|
7
|
+
include Pagination
|
8
|
+
|
9
|
+
document Search::Admin
|
10
|
+
|
11
|
+
def initialize(params = {})
|
12
|
+
super(params.merge(type: 'registry'))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Workarea
|
2
|
+
class RegistryContentSeeds
|
3
|
+
def perform
|
4
|
+
puts 'Adding registry content...'
|
5
|
+
|
6
|
+
create_system_page
|
7
|
+
create_email_content
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_system_page
|
11
|
+
content = Content.for('registries')
|
12
|
+
content.blocks.create!(
|
13
|
+
area: 'default',
|
14
|
+
type: 'html',
|
15
|
+
data: { html: default_page_html }
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_email_content
|
20
|
+
Content::Email.create!(
|
21
|
+
type: 'registry_creation',
|
22
|
+
content: 'Congratulations on your new registry!'
|
23
|
+
)
|
24
|
+
|
25
|
+
Content::Email.create!(
|
26
|
+
type: 'registry_share',
|
27
|
+
content: 'Check out my Registry.'
|
28
|
+
)
|
29
|
+
|
30
|
+
Content::Email.create!(
|
31
|
+
type: 'registry_thank',
|
32
|
+
content: 'Thank you for your Gift.'
|
33
|
+
)
|
34
|
+
|
35
|
+
Content::Email.create!(
|
36
|
+
type: 'registry_gift_receipt',
|
37
|
+
content: 'The following items have been purchased for your Registry.'
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def default_page_html
|
42
|
+
<<-TEXT
|
43
|
+
<h1>Registry Welcome Page</h1>
|
44
|
+
<p>
|
45
|
+
This page will act as a portal for users to begin and continue their Registry Journey.
|
46
|
+
</p>
|
47
|
+
TEXT
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Workarea
|
2
|
+
decorate InventoryAdjustment, with: :registries do
|
3
|
+
def perform
|
4
|
+
super
|
5
|
+
|
6
|
+
cart.items.each do |item|
|
7
|
+
next unless item.registry_item?
|
8
|
+
available_quantity = find_available_quantity(item)
|
9
|
+
|
10
|
+
next if available_quantity >= item.quantity
|
11
|
+
|
12
|
+
if available_quantity == 0
|
13
|
+
cart.remove_item(item.id)
|
14
|
+
@errors << I18n.t('workarea.errors.messages.registry_sku_unavailable', sku: item[:sku], registry: @registry.name)
|
15
|
+
else
|
16
|
+
cart.update_item(item.id, quantity: available_quantity)
|
17
|
+
@errors << I18n.t(
|
18
|
+
'workarea.errors.messages.registry_sku_limited_quantity',
|
19
|
+
quantity: available_quantity,
|
20
|
+
sku: item[:sku],
|
21
|
+
registry: @registry.name
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def find_available_quantity(item)
|
30
|
+
registry_item = find_registry_item(item)
|
31
|
+
|
32
|
+
registry_item&.available_quantity || 0
|
33
|
+
end
|
34
|
+
|
35
|
+
def find_registry_item(item)
|
36
|
+
@registry = Storefront::RegistryViewModel.wrap(
|
37
|
+
Registry.find_by(id: item.registry_id)
|
38
|
+
)
|
39
|
+
|
40
|
+
@registry.items.select { |registry_item| registry_item.sku == item.sku }.first
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Admin
|
3
|
+
class RegistryViewModel < ApplicationViewModel
|
4
|
+
include CommentableViewModel
|
5
|
+
|
6
|
+
def timeline
|
7
|
+
@timeline ||= TimelineViewModel.new(model)
|
8
|
+
end
|
9
|
+
|
10
|
+
def user
|
11
|
+
@user ||= UserViewModel.new(
|
12
|
+
User.find(user_id)
|
13
|
+
)
|
14
|
+
rescue Mongoid::Errors::DocumentNotFound
|
15
|
+
@user = nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Workarea
|
2
|
+
decorate Storefront::Checkout::AddressesViewModel, with: :registries do
|
3
|
+
def registry_address
|
4
|
+
Registry.where(id: registry_id).first.address
|
5
|
+
end
|
6
|
+
|
7
|
+
def registry_address?
|
8
|
+
order.items.map{ | item | item.registry_id }.uniq.one?
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def registry_id
|
14
|
+
order.items.detect(&:registry_id?).registry_id
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Storefront
|
3
|
+
class RegistryEmailViewModel < ApplicationViewModel
|
4
|
+
def to
|
5
|
+
to_email
|
6
|
+
end
|
7
|
+
|
8
|
+
def from
|
9
|
+
from_email
|
10
|
+
end
|
11
|
+
|
12
|
+
def from_name
|
13
|
+
model.from_name || options[:user].try(:name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def from_email
|
17
|
+
model.from_email || options[:user].try(:email)
|
18
|
+
end
|
19
|
+
|
20
|
+
def friendly_from
|
21
|
+
if from_name.blank?
|
22
|
+
from_email
|
23
|
+
else
|
24
|
+
from_name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def friendly_to
|
29
|
+
if to_name.present?
|
30
|
+
"#{to_name} (#{to_email})"
|
31
|
+
else
|
32
|
+
to_email
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Storefront
|
3
|
+
class RegistryItemViewModel < CartItemViewModel
|
4
|
+
delegate :on_sale?, to: :price
|
5
|
+
delegate :has_prices?, :primary_image, :purchasable?, to: :product
|
6
|
+
|
7
|
+
def product
|
8
|
+
@product ||= ProductViewModel.wrap(
|
9
|
+
catalog_product,
|
10
|
+
options.merge(sku: sku)
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def catalog_product
|
15
|
+
@catalog_product ||= Catalog::Product.find(product_id)
|
16
|
+
end
|
17
|
+
|
18
|
+
def template
|
19
|
+
if purchased_by?
|
20
|
+
I18n.t('workarea.storefront.registry.item.purchased')
|
21
|
+
else
|
22
|
+
I18n.t('workarea.storefront.registry.item.unpurchased')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# maintain compatibility with OrderItemViewModel
|
27
|
+
def customized?
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
def original_price
|
32
|
+
price.regular
|
33
|
+
end
|
34
|
+
|
35
|
+
def original_unit_price
|
36
|
+
price.sell
|
37
|
+
end
|
38
|
+
|
39
|
+
def total_price
|
40
|
+
original_unit_price * quantity
|
41
|
+
end
|
42
|
+
|
43
|
+
def price
|
44
|
+
@pricing ||=
|
45
|
+
options[:pricing]&.for_sku(sku, quantity: quantity) ||
|
46
|
+
Pricing::Sku.find_or_create_by(id: sku).find_price(quantity: quantity)
|
47
|
+
end
|
48
|
+
|
49
|
+
def inventory
|
50
|
+
@inventory ||=
|
51
|
+
options[:inventory]&.for_sku(sku) ||
|
52
|
+
Inventory::Sku.find_or_create_by(id: sku)
|
53
|
+
end
|
54
|
+
|
55
|
+
def inventory_status
|
56
|
+
InventoryStatusViewModel.new(inventory).message
|
57
|
+
end
|
58
|
+
|
59
|
+
def insufficient_inventory?
|
60
|
+
inventory.available_to_sell < quantity
|
61
|
+
end
|
62
|
+
|
63
|
+
def received?
|
64
|
+
received.positive?
|
65
|
+
end
|
66
|
+
|
67
|
+
def available_quantity
|
68
|
+
quantity - received
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Storefront
|
3
|
+
class RegistryViewModel < ApplicationViewModel
|
4
|
+
def user
|
5
|
+
@user ||=
|
6
|
+
options[:user] ||
|
7
|
+
UserViewModel.wrap(User.find(user_id))
|
8
|
+
end
|
9
|
+
|
10
|
+
def address
|
11
|
+
model.address ||
|
12
|
+
user.try(:default_shipping_address) ||
|
13
|
+
Address.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def email
|
17
|
+
model.email || user.try(:email)
|
18
|
+
end
|
19
|
+
|
20
|
+
def owner
|
21
|
+
user.name.present? ? user.name : user.email
|
22
|
+
end
|
23
|
+
|
24
|
+
def items
|
25
|
+
@items ||=
|
26
|
+
begin
|
27
|
+
items = if options[:state] == 'purchased'
|
28
|
+
model.purchased_items
|
29
|
+
elsif options[:state] == 'available'
|
30
|
+
model.available_items
|
31
|
+
else
|
32
|
+
model.unpurchased_items
|
33
|
+
end
|
34
|
+
|
35
|
+
items.map do |item|
|
36
|
+
RegistryItemViewModel.wrap(
|
37
|
+
item,
|
38
|
+
options.merge(inventory: inventory, pricing: pricing)
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def state_options
|
45
|
+
[
|
46
|
+
[I18n.t('workarea.storefront.registries.unpurchased_items'), :unpurchased],
|
47
|
+
[I18n.t('workarea.storefront.registries.purchased_items'), :purchased]
|
48
|
+
]
|
49
|
+
end
|
50
|
+
|
51
|
+
def event_date
|
52
|
+
model.event_date.try(:to_date)
|
53
|
+
end
|
54
|
+
|
55
|
+
def registry_label
|
56
|
+
I18n.t('workarea.storefront.registries.registry_label', name: model.name)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def inventory
|
62
|
+
@inventory ||= Inventory::Collection.new(skus)
|
63
|
+
end
|
64
|
+
|
65
|
+
def pricing
|
66
|
+
@pricing ||= Pricing::Collection.new(skus)
|
67
|
+
end
|
68
|
+
|
69
|
+
def skus
|
70
|
+
model.items.map(&:sku)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|