Skip to content

Commit b319056

Browse files
Merge branch 'main' into patch-2
2 parents d0cb88e + f91d04d commit b319056

36 files changed

+380
-14
lines changed

app/controllers/admin/shop_items_controller.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def set_shop_item_types
6060

6161
def available_shop_item_types
6262
[
63+
"ShopItem::Accessory",
6364
"ShopItem::HCBGrant",
6465
"ShopItem::HCBPreauthGrant",
6566
"ShopItem::HQMailItem",
@@ -112,7 +113,9 @@ def shop_item_params
112113
:hcb_merchant_lock,
113114
:hcb_preauthorization_instructions,
114115
:agh_contents,
115-
:image
116+
:image,
117+
:buyable_by_self,
118+
attached_shop_item_ids: []
116119
)
117120
end
118121
end

app/controllers/admin/shop_orders_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def index
1515
end
1616

1717
# Base query
18-
orders = ShopOrder.includes(:shop_item, :user)
18+
orders = ShopOrder.includes(:shop_item, :user, :accessory_orders)
1919

2020
# Apply view-specific scopes
2121
case @view

app/controllers/shop_controller.rb

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@ def index
1414
.select { |item| item.enabled_in_region?(@user_region) }
1515
.first
1616
end
17-
@shop_items = ShopItem.all.includes(:image_attachment)
17+
@shop_items = ShopItem.buyable_standalone.includes(:image_attachment)
1818
@shop_items = @shop_items.where.not(type: "ShopItem::FreeStickers") if user_ordered_free_stickers?
1919
@user_balance = current_user.balance
2020
end
2121

2222
def my_orders
23-
@orders = current_user.shop_orders.includes(shop_item: { image_attachment: :blob }).order(id: :desc)
23+
@orders = current_user.shop_orders
24+
.where(parent_order_id: nil)
25+
.includes(:accessory_orders, shop_item: { image_attachment: :blob })
26+
.order(id: :desc)
2427
end
2528

2629
def cancel_order
@@ -35,6 +38,7 @@ def cancel_order
3538

3639
def order
3740
@shop_item = ShopItem.find(params[:shop_item_id])
41+
@accessories = @shop_item.available_accessories.includes(:image_attachment)
3842
end
3943

4044
def update_region
@@ -43,7 +47,7 @@ def update_region
4347
current_user.update!(region: region)
4448

4549
@user_region = region
46-
@shop_items = ShopItem.all.includes(:image_attachment)
50+
@shop_items = ShopItem.buyable_standalone.includes(:image_attachment)
4751
@shop_items = @shop_items.where.not(type: "ShopItem::FreeStickers") if user_ordered_free_stickers?
4852
@user_balance = current_user.balance
4953
@featured_item = unless user_ordered_free_stickers?
@@ -65,12 +69,20 @@ def update_region
6569
def create_order
6670
@shop_item = ShopItem.find(params[:shop_item_id])
6771
quantity = params[:quantity].to_i
72+
accessory_ids = Array(params[:accessory_ids]).map(&:to_i).reject(&:zero?)
6873

6974
if quantity <= 0
7075
redirect_to shop_order_path(shop_item_id: @shop_item.id), alert: "Quantity must be greater than 0"
7176
return
7277
end
7378

79+
# Validate accessories belong to this item
80+
@accessories = if accessory_ids.any?
81+
@shop_item.available_accessories.where(id: accessory_ids)
82+
else
83+
[]
84+
end
85+
7486
# Create the order
7587
# This is a simplified version. In a real app, you'd want to:
7688
# 1. Check stock
@@ -81,12 +93,29 @@ def create_order
8193
@order = current_user.shop_orders.new(
8294
shop_item: @shop_item,
8395
quantity: quantity,
84-
frozen_address: selected_address
96+
frozen_address: selected_address,
97+
accessory_ids: @accessories.pluck(:id)
8598
)
8699

87100
@order.aasm_state = "pending" if @order.respond_to?(:aasm_state=)
88101

89-
if @order.save
102+
begin
103+
ActiveRecord::Base.transaction do
104+
@order.save!
105+
106+
# Create orders for each accessory
107+
@accessories.each do |accessory|
108+
accessory_order = current_user.shop_orders.new(
109+
shop_item: accessory,
110+
quantity: 1,
111+
frozen_address: selected_address,
112+
parent_order_id: @order.id
113+
)
114+
accessory_order.aasm_state = "pending" if accessory_order.respond_to?(:aasm_state=)
115+
accessory_order.save!
116+
end
117+
end
118+
90119
if @shop_item.is_a?(ShopItem::FreeStickers)
91120
begin
92121
@shop_item.fulfill!(@order)
@@ -99,8 +128,8 @@ def create_order
99128
end
100129
end
101130
redirect_to shop_my_orders_path, notice: "Order placed successfully!"
102-
else
103-
redirect_to shop_order_path(shop_item_id: @shop_item.id), alert: "Failed to place order: #{@order.errors.full_messages.join(', ')}"
131+
rescue ActiveRecord::RecordInvalid => e
132+
redirect_to shop_order_path(shop_item_id: @shop_item.id), alert: "Failed to place order: #{e.record.errors.full_messages.join(', ')}"
104133
end
105134
end
106135

app/models/post/devlog.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# duration_seconds :integer
88
# hackatime_projects_key_snapshot :text
99
# hackatime_pulled_at :datetime
10+
# scrapbook_url :string
1011
# created_at :datetime not null
1112
# updated_at :datetime not null
1213
#

app/models/shop_item.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#
55
# id :bigint not null, primary key
66
# agh_contents :jsonb
7+
# attached_shop_item_ids :bigint default([]), is an Array
8+
# buyable_by_self :boolean default(TRUE)
79
# description :string
810
# enabled :boolean
911
# enabled_au :boolean
@@ -67,6 +69,7 @@ class ShopItem < ApplicationRecord
6769
scope :shown_in_carousel, -> { where(show_in_carousel: true) }
6870
scope :manually_fulfilled, -> { where(type: MANUAL_FULFILLMENT_TYPES) }
6971
scope :enabled, -> { where(enabled: true) }
72+
scope :buyable_standalone, -> { where.not(type: "ShopItem::Accessory").or(where(buyable_by_self: true)) }
7073

7174
belongs_to :seller, class_name: "User", foreign_key: :user_id, optional: true
7275

@@ -138,4 +141,12 @@ def remaining_stock
138141
def out_of_stock?
139142
limited? && remaining_stock && remaining_stock <= 0
140143
end
144+
145+
def available_accessories
146+
ShopItem::Accessory.where("? = ANY(attached_shop_item_ids)", id).where(enabled: true)
147+
end
148+
149+
def has_accessories?
150+
available_accessories.exists?
151+
end
141152
end

app/models/shop_item/accessory.rb

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# == Schema Information
2+
#
3+
# Table name: shop_items
4+
#
5+
# id :bigint not null, primary key
6+
# agh_contents :jsonb
7+
# attached_shop_item_ids :bigint default([]), is an Array
8+
# buyable_by_self :boolean default(TRUE)
9+
# description :string
10+
# enabled :boolean
11+
# enabled_au :boolean
12+
# enabled_ca :boolean
13+
# enabled_eu :boolean
14+
# enabled_in :boolean
15+
# enabled_uk :boolean
16+
# enabled_us :boolean
17+
# enabled_xx :boolean
18+
# hacker_score :integer
19+
# hcb_category_lock :string
20+
# hcb_keyword_lock :string
21+
# hcb_merchant_lock :string
22+
# hcb_preauthorization_instructions :text
23+
# internal_description :string
24+
# limited :boolean
25+
# max_qty :integer
26+
# name :string
27+
# one_per_person_ever :boolean
28+
# payout_percentage :integer default(0)
29+
# price_offset_au :decimal(, )
30+
# price_offset_ca :decimal(, )
31+
# price_offset_eu :decimal(, )
32+
# price_offset_in :decimal(, )
33+
# price_offset_uk :decimal(10, 2)
34+
# price_offset_us :decimal(, )
35+
# price_offset_xx :decimal(, )
36+
# sale_percentage :integer
37+
# show_in_carousel :boolean
38+
# site_action :integer
39+
# special :boolean
40+
# stock :integer
41+
# ticket_cost :decimal(, )
42+
# type :string
43+
# unlock_on :date
44+
# usd_cost :decimal(, )
45+
# created_at :datetime not null
46+
# updated_at :datetime not null
47+
# user_id :bigint
48+
#
49+
# Indexes
50+
#
51+
# index_shop_items_on_user_id (user_id)
52+
#
53+
# Foreign Keys
54+
#
55+
# fk_rails_... (user_id => users.id)
56+
#
57+
class ShopItem::Accessory < ShopItem
58+
validate :must_have_attached_items_if_not_buyable_by_self
59+
60+
def attached_shop_items
61+
return ShopItem.none if attached_shop_item_ids.blank?
62+
63+
ShopItem.where(id: attached_shop_item_ids)
64+
end
65+
66+
def can_be_purchased_standalone?
67+
buyable_by_self?
68+
end
69+
70+
def can_attach_to?(shop_item)
71+
attached_shop_item_ids.include?(shop_item.id)
72+
end
73+
74+
def total_cost_with(parent_item)
75+
return nil unless can_attach_to?(parent_item)
76+
77+
ticket_cost + parent_item.ticket_cost
78+
end
79+
80+
def standalone_cost
81+
return nil unless can_be_purchased_standalone?
82+
83+
ticket_cost
84+
end
85+
86+
private
87+
88+
def must_have_attached_items_if_not_buyable_by_self
89+
if !buyable_by_self? && attached_shop_item_ids.blank?
90+
errors.add(:attached_shop_item_ids, "must have at least one attached item when not buyable by self")
91+
end
92+
end
93+
end

app/models/shop_item/free_stickers.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#
55
# id :bigint not null, primary key
66
# agh_contents :jsonb
7+
# attached_shop_item_ids :bigint default([]), is an Array
8+
# buyable_by_self :boolean default(TRUE)
79
# description :string
810
# enabled :boolean
911
# enabled_au :boolean

app/models/shop_item/h_c_b_grant.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#
55
# id :bigint not null, primary key
66
# agh_contents :jsonb
7+
# attached_shop_item_ids :bigint default([]), is an Array
8+
# buyable_by_self :boolean default(TRUE)
79
# description :string
810
# enabled :boolean
911
# enabled_au :boolean

app/models/shop_item/h_c_b_preauth_grant.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#
55
# id :bigint not null, primary key
66
# agh_contents :jsonb
7+
# attached_shop_item_ids :bigint default([]), is an Array
8+
# buyable_by_self :boolean default(TRUE)
79
# description :string
810
# enabled :boolean
911
# enabled_au :boolean

app/models/shop_item/h_q_mail_item.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#
55
# id :bigint not null, primary key
66
# agh_contents :jsonb
7+
# attached_shop_item_ids :bigint default([]), is an Array
8+
# buyable_by_self :boolean default(TRUE)
79
# description :string
810
# enabled :boolean
911
# enabled_au :boolean

0 commit comments

Comments
 (0)