auctify 1.0.0 → 1.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c19c38113580070b4a0e6d9914b22c042fb3024e2a96193a7004fd61859985c3
4
- data.tar.gz: '080f74640d3c40935a0faf304c120cb294e896e000ffa1cc822a769d077f3fbf'
3
+ metadata.gz: 13a52b4af50580a4b82abdb1c7fd04686803f3a59d648b2a0fb18bc7acb46875
4
+ data.tar.gz: c33891089eb4f6e53d101b033442dc56dd62966a2b0aef5d1173e73c0070335d
5
5
  SHA512:
6
- metadata.gz: b5dc577b91bd6ba035187bbf272f52f744a6ee7832c90c998e27c7eb63324c98b7bfa1eecb259f21522d0dae8accde3ef112a8166d58bc2ece2a6cbfd6edafa2
7
- data.tar.gz: 67f7b5f692569e542eaa5a59727e15c9e602a1d022c69156046acf7e343f4f5acecad42b1e7adfd44bc8b7bb317dcd293bb5aec7c4d9d6e28078b829e24dd8b7
6
+ metadata.gz: 33f938020a0879f29ce53ac4f0a926138f9c904cdcb435ecdf4ac79119300e450394861c12f611e6248ac5136ab7e94516a3232d9451bf339a682509ce2eac92
7
+ data.tar.gz: a68e770af42e0c5be75ff52e47bccc428d80be1c49b5744623cd26d0c0aa5b01d6700d5e385f1e0ad3e41448aeaf7ff66efbb8b5fd0414b3c562e35041b64776
data/README.md CHANGED
@@ -65,7 +65,7 @@ If `item.owner` exists, it is used as `auction.seller` (or Select from all aucit
65
65
  - item can have category and user can select auction by categories.
66
66
  - SalePack can be `(un)published /public`
67
67
  - SalePack can be `open (adding items , bidding)/ closed(bidding ended)`
68
- - auctioneer_commission is in % and adds to sold_price in checkout
68
+ - auctioneer_commision_from_buyer is in % and adds to sold_price in checkout
69
69
  - auction have `start_time` and `end_time`
70
70
  - auction can be `highlighted` for cover pages
71
71
  - auction stores all bids history even those cancelled by admin
@@ -125,7 +125,7 @@ end
125
125
  ```ruby
126
126
  Auctify.configure do |config|
127
127
  config.autoregister_as_bidders_all_instances_of_classes = ["User"] # default is []
128
- config.auction_prolonging_limit = 10.minutes # default is 1.minute
128
+ config.auction_prolonging_limit_in_seconds = 10.minutes # default is 1.minute, can be overriden in `SalePack#auction_prolonging_limit_in_seconds` attribute
129
129
  config.auctioneer_commission_in_percent = 10 # so buyer will pay: auction.current_price * ((100 + 10)/100)
130
130
  config.autofinish_auction_after_bidding = true # after `auction.close_bidding!` immediatelly proces result to `auction.sold_in_auction!` or `auction.not_sold_in_auction!`; default false
131
131
  config.when_to_notify_bidders_before_end_of_bidding = 30.minutes # default `nil` => no notifying
@@ -7,7 +7,11 @@ module Auctify
7
7
  before_action :find_auction, except: [:index]
8
8
 
9
9
  def show
10
- render_record @auction
10
+ if params[:updated_at].present? && params[:updated_at].match?(/\A\d+\z/) && params[:updated_at].to_i == @auction.updated_at.to_i
11
+ render json: { current: true }, status: 200
12
+ else
13
+ render_record @auction
14
+ end
11
15
  end
12
16
 
13
17
  def bids
@@ -20,7 +24,12 @@ module Auctify
20
24
  .update_all(dont_confirm_bids: true)
21
25
  end
22
26
 
23
- render_record @auction.reload, success: true
27
+ @auction.reload
28
+
29
+ winning_bid_id = @auction.winning_bid.try(:id)
30
+ overbid_by_limit = winning_bid_id && new_bid.id && winning_bid_id != new_bid.id
31
+
32
+ render_record @auction, success: true, overbid_by_limit: overbid_by_limit
24
33
  else
25
34
  render_record @auction, bid: new_bid, status: 400
26
35
  end
@@ -50,9 +59,13 @@ module Auctify
50
59
  { bidder: current_user }
51
60
  end
52
61
 
53
- def render_record(auction, bid: nil, status: 200, success: nil)
62
+ def render_record(auction, bid: nil, status: 200, success: nil, overbid_by_limit: nil)
54
63
  render json: {
55
- data: cell("#{global_namespace_path}/auctify/auctions/form", auction, bid: bid, success: success).show
64
+ data: cell("#{global_namespace_path}/auctify/auctions/form",
65
+ auction,
66
+ bid: bid,
67
+ success: success,
68
+ overbid_by_limit: overbid_by_limit).show
56
69
  }, status: status
57
70
  end
58
71
  end
@@ -46,8 +46,11 @@ module Auctify
46
46
 
47
47
  # DELETE /bidder_registrations/1
48
48
  def destroy
49
- @bidder_registration.destroy
50
- redirect_to auctify_bidder_registrations_url, notice: "Bidder registration was successfully destroyed."
49
+ if @bidder_registration.destroy
50
+ redirect_to auctify_bidder_registrations_url, notice: "Bidder registration was successfully destroyed."
51
+ else
52
+ render json: { errors: @bidder_registration.errors }
53
+ end
51
54
  end
52
55
 
53
56
  private
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Auctify
4
+ class AutobidFillerJob < Auctify::ApplicationJob
5
+ queue_as :default
6
+
7
+ def perform
8
+ reg_ids = Auctify::Bid.pluck(:registration_id).uniq
9
+ registrations_with_bids = Auctify::BidderRegistration.where(id: reg_ids)
10
+
11
+ registrations_with_bids.find_each { |reg| reg.fillup_autobid_flags! }
12
+ end
13
+ end
14
+ end
@@ -12,11 +12,11 @@ module Auctify
12
12
  return
13
13
  end
14
14
 
15
- if auction.currently_ends_at <= Time.current
16
- auction.close_bidding! if auction.in_sale?
17
- else
18
- self.class.set(wait_until: auction.currently_ends_at)
19
- .perform_later(auction_id: auction.id)
15
+ return if Time.current < auction.currently_ends_at
16
+
17
+ Auctify::Sale::Auction.with_advisory_lock("closing_auction_#{auction_id}") do
18
+ # can wait unitl other BCJob release lock and than continue!
19
+ auction.close_bidding! if auction.reload.in_sale?
20
20
  end
21
21
  end
22
22
  end
@@ -14,7 +14,7 @@ module Auctify
14
14
  return
15
15
  end
16
16
 
17
- notify_time = auction.ends_at - Auctify.configuration.when_to_notify_bidders_before_end_of_bidding
17
+ notify_time = auction.bidding_is_close_to_end_notification_time
18
18
 
19
19
  return unless auction.open_for_bids?
20
20
 
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Auctify
4
+ class EnsureAuctionsClosingJob < ApplicationJob
5
+ queue_as :critical
6
+
7
+ def perform
8
+ auctions = ::Auctify::Sale::Auction.in_sale
9
+ .where("currently_ends_at <= ?", Time.current + checking_period_to_future)
10
+ auctions.each do |auction|
11
+ if auction.currently_ends_at <= Time.current
12
+ Auctify::BiddingCloserJob.perform_now(auction_id: auction.id)
13
+ else
14
+ Auctify::BiddingCloserJob.set(wait_until: auction.currently_ends_at).perform_later(auction_id: auction.id)
15
+ end
16
+ end
17
+ end
18
+
19
+ private
20
+ def checking_period_to_future
21
+ Auctify.configuration.auction_prolonging_limit_in_seconds || 5.minutes
22
+ end
23
+ end
24
+ end
@@ -10,6 +10,14 @@ module Auctify
10
10
  scope :with_limit, -> { where.not(max_price: nil) }
11
11
 
12
12
  validate :price_is_not_bigger_then_max_price
13
+ validate :price_is_rounded
14
+
15
+ def <=>(other)
16
+ r = (self.price <=> other.price)
17
+ r = (self.created_at <=> other.created_at) if r.zero?
18
+ r = (self.id <=> other.id) if r.zero?
19
+ r
20
+ end
13
21
 
14
22
  def cancel!
15
23
  update!(cancelled: true)
@@ -18,7 +26,11 @@ module Auctify
18
26
  end
19
27
 
20
28
  def with_limit?
21
- max_price.present?
29
+ limit.present?
30
+ end
31
+
32
+ def limit
33
+ max_price
22
34
  end
23
35
 
24
36
  def bade_at
@@ -29,6 +41,12 @@ module Auctify
29
41
  errors.add(:price, :must_be_lower_or_equal_max_price) if max_price && max_price < price
30
42
  end
31
43
 
44
+ def price_is_rounded
45
+ round_to = configuration.require_bids_to_be_rounded_to
46
+ errors.add(:price, :must_be_rounded_to, { round_to: round_to }) if price && (price != round_it_to(price, round_to))
47
+ errors.add(:max_price, :must_be_rounded_to, { round_to: round_to }) if max_price && (max_price != round_it_to(max_price, round_to))
48
+ end
49
+
32
50
  def bidder=(auctified_model)
33
51
  errors.add(:bidder, :not_auctified) unless auctified_model.class.included_modules.include?(Auctify::Behavior::Buyer)
34
52
  raise "There is already registration for this bid!" if registration.present?
@@ -46,6 +64,12 @@ module Auctify
46
64
  def configuration
47
65
  Auctify.configuration
48
66
  end
67
+
68
+ private
69
+ def round_it_to(amount, smallest_amount)
70
+ smallest_amount = smallest_amount.to_i
71
+ (smallest_amount * ((amount + (smallest_amount / 2)).to_i / smallest_amount))
72
+ end
49
73
  end
50
74
  end
51
75
 
@@ -60,6 +84,7 @@ end
60
84
  # created_at :datetime not null
61
85
  # updated_at :datetime not null
62
86
  # cancelled :boolean default(FALSE)
87
+ # autobid :boolean default(FALSE)
63
88
  #
64
89
  # Indexes
65
90
  #
@@ -49,6 +49,25 @@ module Auctify
49
49
 
50
50
  validate :auction_is_in_allowed_state, on: :create
51
51
 
52
+ def fillup_autobid_flags!
53
+ current_limit = 0
54
+ ordered_applied_bids.reverse_each do |bid|
55
+ if bid.with_limit?
56
+ if current_limit < bid.limit
57
+ # increase of limit
58
+ bid.autobid = false
59
+ current_limit = bid.limit
60
+ else
61
+ # same limit, same registration, younger bid => autobid
62
+ bid.autobid = true
63
+ end
64
+ else
65
+ bid.autobid = false
66
+ end
67
+ bid.save!(validate: false)
68
+ end
69
+ end
70
+
52
71
  private
53
72
  def auction_is_in_allowed_state
54
73
  unless auction && auction.allows_new_bidder_registrations?
@@ -60,8 +60,6 @@ module Auctify
60
60
  transitions from: :accepted, to: :in_sale
61
61
 
62
62
  after do
63
- set_bidding_closer_job
64
- set_bidding_is_close_to_end_job
65
63
  after_start_sale
66
64
  end
67
65
  end
@@ -119,6 +117,7 @@ module Auctify
119
117
  validate :forbidden_changes
120
118
 
121
119
  after_create :autoregister_bidders
120
+ after_save :create_jobs
122
121
  before_destroy :forbid_destroy_if_there_are_bids, prepend: true
123
122
 
124
123
  def bidders
@@ -217,9 +216,11 @@ module Auctify
217
216
  bidding_result.current_minimal_bid
218
217
  end
219
218
 
220
- def current_max_price_for(bidder)
221
- last_bidder_mx_bid = ordered_applied_bids.with_limit
222
- .detect { |bid| bid.bidder == bidder }
219
+ def current_max_price_for(bidder, bids_array: nil)
220
+ bids_array ||= ordered_applied_bids.with_limit
221
+
222
+ last_bidder_mx_bid = bids_array.detect { |bid| !bid.max_price.nil? && bid.bidder == bidder }
223
+
223
224
  last_bidder_mx_bid.blank? ? 0 : last_bidder_mx_bid.max_price
224
225
  end
225
226
 
@@ -244,9 +245,8 @@ module Auctify
244
245
  applied_bids_count.positive?
245
246
  end
246
247
 
247
- def set_bidding_closer_job
248
- Auctify::BiddingCloserJob.set(wait_until: currently_ends_at)
249
- .perform_later(auction_id: id)
248
+ def auction_prolonging_limit_in_seconds
249
+ pack&.auction_prolonging_limit_in_seconds || Auctify.configuration.auction_prolonging_limit_in_seconds
250
250
  end
251
251
 
252
252
  private
@@ -311,7 +311,7 @@ module Auctify
311
311
  end
312
312
 
313
313
  def extend_end_time(bid_time)
314
- new_end_time = bid_time + Auctify.configuration.auction_prolonging_limit
314
+ new_end_time = bid_time + auction_prolonging_limit_in_seconds
315
315
  self.currently_ends_at = [currently_ends_at, new_end_time].max
316
316
  end
317
317
 
@@ -326,16 +326,17 @@ module Auctify
326
326
  end
327
327
  end
328
328
 
329
- def set_bidding_is_close_to_end_job
329
+ def bidding_is_close_to_end_notification_time
330
330
  configured_period = Auctify.configuration.when_to_notify_bidders_before_end_of_bidding
331
- return if configured_period.blank?
332
- notify_time = ends_at - configured_period
333
- Auctify::BiddingIsCloseToEndNotifierJob.set(wait_until: notify_time)
334
- .perform_later(auction_id: id)
331
+ return nil if configured_period.blank?
332
+
333
+ ends_at - configured_period
335
334
  end
336
335
 
337
336
  def forbidden_changes
338
337
  ATTRIBUTES_UNMUTABLE_AT_SOME_STATE.each do |att|
338
+ next if configuration.allow_changes_on_auction_with_bids_for_attributes.include?(att.to_sym)
339
+
339
340
  if changes[att].present? && locked_for_modifications?
340
341
  errors.add(att, :no_modification_allowed_now)
341
342
  write_attribute(att, changes[att].first)
@@ -349,6 +350,20 @@ module Auctify
349
350
  def forbid_destroy_if_there_are_bids
350
351
  errors.add(:base, :you_cannot_delete_auction_with_bids) if bids.any?
351
352
  end
353
+
354
+ def create_jobs(force = false)
355
+ return unless in_sale?
356
+
357
+ currently_ends_at_changes = saved_changes["currently_ends_at"]
358
+
359
+ if force || currently_ends_at_changes.present?
360
+ if (notify_time = bidding_is_close_to_end_notification_time).present?
361
+ # remove_old job is unsupported in ActiveJob
362
+ Auctify::BiddingIsCloseToEndNotifierJob.set(wait_until: notify_time)
363
+ .perform_later(auction_id: id)
364
+ end
365
+ end
366
+ end
352
367
  end
353
368
  end
354
369
  end
@@ -357,37 +372,51 @@ end
357
372
  #
358
373
  # Table name: auctify_sales
359
374
  #
360
- # id :bigint(8) not null, primary key
361
- # seller_type :string
362
- # seller_id :integer
363
- # buyer_type :string
364
- # buyer_id :integer
365
- # item_id :integer not null
366
- # created_at :datetime not null
367
- # updated_at :datetime not null
368
- # type :string default("Auctify::Sale::Base")
369
- # aasm_state :string default("offered"), not null
370
- # offered_price :decimal(, )
371
- # current_price :decimal(, )
372
- # sold_price :decimal(, )
373
- # bid_steps_ladder :json
374
- # reserve_price :decimal(, )
375
- # pack_id :bigint(8)
376
- # ends_at :datetime
377
- # position :integer
378
- # number :string
379
- # currently_ends_at :datetime
380
- # published :boolean default(FALSE)
381
- # featured :boolean default(FALSE)
382
- # slug :string
383
- # contract_number :string
384
- # commission_in_percent :integer
385
- # winner_type :string
386
- # winner_id :bigint(8)
387
- # applied_bids_count :integer default(0)
388
- # sold_at :datetime
389
- # current_winner_type :string
390
- # current_winner_id :bigint(8)
375
+ # id :bigint(8) not null, primary key
376
+ # seller_type :string
377
+ # seller_id :integer
378
+ # buyer_type :string
379
+ # buyer_id :integer
380
+ # item_id :integer not null
381
+ # created_at :datetime not null
382
+ # updated_at :datetime not null
383
+ # type :string default("Auctify::Sale::Base")
384
+ # aasm_state :string default("offered"), not null
385
+ # offered_price :decimal(, )
386
+ # current_price :decimal(, )
387
+ # sold_price :decimal(, )
388
+ # bid_steps_ladder :json
389
+ # reserve_price :decimal(, )
390
+ # pack_id :bigint(8)
391
+ # ends_at :datetime
392
+ # position :integer
393
+ # number :string
394
+ # currently_ends_at :datetime
395
+ # published :boolean default(FALSE)
396
+ # slug :string
397
+ # contract_number :string
398
+ # seller_commission_in_percent :integer
399
+ # winner_type :string
400
+ # winner_id :bigint(8)
401
+ # applied_bids_count :integer default(0)
402
+ # sold_at :datetime
403
+ # current_winner_type :string
404
+ # current_winner_id :bigint(8)
405
+ # buyer_commission_in_percent :integer
406
+ # featured :integer
407
+ #
408
+ # Indexes
409
+ #
410
+ # index_auctify_sales_on_buyer_type_and_buyer_id (buyer_type,buyer_id)
411
+ # index_auctify_sales_on_currently_ends_at (currently_ends_at)
412
+ # index_auctify_sales_on_featured (featured)
413
+ # index_auctify_sales_on_pack_id (pack_id)
414
+ # index_auctify_sales_on_position (position)
415
+ # index_auctify_sales_on_published (published)
416
+ # index_auctify_sales_on_seller_type_and_seller_id (seller_type,seller_id)
417
+ # index_auctify_sales_on_slug (slug) UNIQUE
418
+ # index_auctify_sales_on_winner_type_and_winner_id (winner_type,winner_id)
419
+ #
391
420
 
392
421
  #
393
422
  # Indexes
@@ -4,8 +4,7 @@ module Auctify
4
4
  module Sale
5
5
  class Base < ApplicationRecord
6
6
  include Folio::FriendlyId
7
- include Folio::Positionable
8
- include Folio::Featurable::Basic
7
+ include Folio::Featurable::WithPosition
9
8
  include Folio::Publishable::Basic
10
9
 
11
10
  self.table_name = "auctify_sales"
@@ -33,6 +32,7 @@ module Auctify
33
32
  validate :validate_offered_price_when_published
34
33
 
35
34
  scope :not_sold, -> { where(sold_price: nil) }
35
+ scope :ordered, -> { order(currently_ends_at: :asc, id: :asc) }
36
36
 
37
37
  # need auction scopes here because of has_many :sales, class_name: "Auctify::Sale::Base"
38
38
  scope :auctions_open_for_bids, -> do
@@ -92,10 +92,14 @@ module Auctify
92
92
  save
93
93
  end
94
94
 
95
- def auctioneer_commission
95
+ def auctioneer_commision_from_seller
96
+ (seller_commission_in_percent || 0) * 0.01 * offered_price
97
+ end
98
+
99
+ def auctioneer_commision_from_buyer
96
100
  return nil if sold_price.nil?
97
101
 
98
- percent = commission_in_percent \
102
+ percent = buyer_commission_in_percent \
99
103
  || (pack&.commission_in_percent) \
100
104
  || Auctify.configuration.auctioneer_commission_in_percent
101
105
 
@@ -164,37 +168,38 @@ end
164
168
  #
165
169
  # Table name: auctify_sales
166
170
  #
167
- # id :bigint(8) not null, primary key
168
- # seller_type :string
169
- # seller_id :integer
170
- # buyer_type :string
171
- # buyer_id :integer
172
- # item_id :integer not null
173
- # created_at :datetime not null
174
- # updated_at :datetime not null
175
- # type :string default("Auctify::Sale::Base")
176
- # aasm_state :string default("offered"), not null
177
- # offered_price :decimal(, )
178
- # current_price :decimal(, )
179
- # sold_price :decimal(, )
180
- # bid_steps_ladder :json
181
- # reserve_price :decimal(, )
182
- # pack_id :bigint(8)
183
- # ends_at :datetime
184
- # position :integer
185
- # number :string
186
- # currently_ends_at :datetime
187
- # published :boolean default(FALSE)
188
- # featured :boolean default(FALSE)
189
- # slug :string
190
- # contract_number :string
191
- # commission_in_percent :integer
192
- # winner_type :string
193
- # winner_id :bigint(8)
194
- # applied_bids_count :integer default(0)
195
- # sold_at :datetime
196
- # current_winner_type :string
197
- # current_winner_id :bigint(8)
171
+ # id :bigint(8) not null, primary key
172
+ # seller_type :string
173
+ # seller_id :integer
174
+ # buyer_type :string
175
+ # buyer_id :integer
176
+ # item_id :integer not null
177
+ # created_at :datetime not null
178
+ # updated_at :datetime not null
179
+ # type :string default("Auctify::Sale::Base")
180
+ # aasm_state :string default("offered"), not null
181
+ # offered_price :decimal(, )
182
+ # current_price :decimal(, )
183
+ # sold_price :decimal(, )
184
+ # bid_steps_ladder :json
185
+ # reserve_price :decimal(, )
186
+ # pack_id :bigint(8)
187
+ # ends_at :datetime
188
+ # position :integer
189
+ # number :string
190
+ # currently_ends_at :datetime
191
+ # published :boolean default(FALSE)
192
+ # slug :string
193
+ # contract_number :string
194
+ # seller_commission_in_percent :integer
195
+ # winner_type :string
196
+ # winner_id :bigint(8)
197
+ # applied_bids_count :integer default(0)
198
+ # sold_at :datetime
199
+ # current_winner_type :string
200
+ # current_winner_id :bigint(8)
201
+ # buyer_commission_in_percent :integer
202
+ # featured :integer
198
203
  #
199
204
  # Indexes
200
205
  #
@@ -23,6 +23,12 @@ module Auctify
23
23
  end
24
24
 
25
25
  event :start_sale do
26
+ before do
27
+ self.current_price = self.offered_price
28
+ self.currently_ends_at = self.ends_at
29
+ self.buyer = nil
30
+ end
31
+
26
32
  transitions from: :accepted, to: :in_sale
27
33
  end
28
34
 
@@ -51,37 +57,38 @@ end
51
57
  #
52
58
  # Table name: auctify_sales
53
59
  #
54
- # id :bigint(8) not null, primary key
55
- # seller_type :string
56
- # seller_id :integer
57
- # buyer_type :string
58
- # buyer_id :integer
59
- # item_id :integer not null
60
- # created_at :datetime not null
61
- # updated_at :datetime not null
62
- # type :string default("Auctify::Sale::Base")
63
- # aasm_state :string default("offered"), not null
64
- # offered_price :decimal(, )
65
- # current_price :decimal(, )
66
- # sold_price :decimal(, )
67
- # bid_steps_ladder :json
68
- # reserve_price :decimal(, )
69
- # pack_id :bigint(8)
70
- # ends_at :datetime
71
- # position :integer
72
- # number :string
73
- # currently_ends_at :datetime
74
- # published :boolean default(FALSE)
75
- # featured :boolean default(FALSE)
76
- # slug :string
77
- # contract_number :string
78
- # commission_in_percent :integer
79
- # winner_type :string
80
- # winner_id :bigint(8)
81
- # applied_bids_count :integer default(0)
82
- # sold_at :datetime
83
- # current_winner_type :string
84
- # current_winner_id :bigint(8)
60
+ # id :bigint(8) not null, primary key
61
+ # seller_type :string
62
+ # seller_id :integer
63
+ # buyer_type :string
64
+ # buyer_id :integer
65
+ # item_id :integer not null
66
+ # created_at :datetime not null
67
+ # updated_at :datetime not null
68
+ # type :string default("Auctify::Sale::Base")
69
+ # aasm_state :string default("offered"), not null
70
+ # offered_price :decimal(, )
71
+ # current_price :decimal(, )
72
+ # sold_price :decimal(, )
73
+ # bid_steps_ladder :json
74
+ # reserve_price :decimal(, )
75
+ # pack_id :bigint(8)
76
+ # ends_at :datetime
77
+ # position :integer
78
+ # number :string
79
+ # currently_ends_at :datetime
80
+ # published :boolean default(FALSE)
81
+ # slug :string
82
+ # contract_number :string
83
+ # seller_commission_in_percent :integer
84
+ # winner_type :string
85
+ # winner_id :bigint(8)
86
+ # applied_bids_count :integer default(0)
87
+ # sold_at :datetime
88
+ # current_winner_type :string
89
+ # current_winner_id :bigint(8)
90
+ # buyer_commission_in_percent :integer
91
+ # featured :integer
85
92
  #
86
93
  # Indexes
87
94
  #
@@ -29,6 +29,7 @@ module Auctify
29
29
  numericality: { greater_than_or_equal: 0, less_than: 60 }
30
30
 
31
31
  validate :validate_start_and_end_dates
32
+ validate :sales_ends_in_pack_time_frame
32
33
 
33
34
  scope :ordered, -> { order(start_date: :desc, id: :desc) }
34
35
 
@@ -57,6 +58,22 @@ module Auctify
57
58
  end
58
59
  end
59
60
 
61
+ def shift_sales_by_minutes!(shift_in_minutes)
62
+ self.transaction do
63
+ sales.each do |sale|
64
+ sale.update!(ends_at: sale.ends_at + shift_in_minutes.minutes)
65
+
66
+ validate_sale_ends_in_time_frame(sale)
67
+ raise ActiveRecord::RecordInvalid if errors[:sales].present?
68
+ end
69
+ end
70
+ sales.reload
71
+ end
72
+
73
+ def time_frame
74
+ (start_date.to_time..(end_date.to_time + 1.day))
75
+ end
76
+
60
77
  private
61
78
  def validate_start_and_end_dates
62
79
  if start_date.present? && end_date.present? && start_date > end_date
@@ -64,11 +81,28 @@ module Auctify
64
81
  end
65
82
  end
66
83
 
84
+ def sales_ends_in_pack_time_frame
85
+ return if changes["start_date"].present? || changes["end_date"]
86
+
87
+ sales.select(:id, :slug, :ends_at).each do |sale|
88
+ validate_sale_ends_in_time_frame(sale)
89
+ end
90
+ end
91
+
67
92
  def set_commission
68
93
  return if self.commission_in_percent.present?
69
94
 
70
95
  self.commission_in_percent = Auctify.configuration.auctioneer_commission_in_percent
71
96
  end
97
+
98
+ def validate_sale_ends_in_time_frame(sale)
99
+ unless time_frame.cover?(sale.ends_at)
100
+ errors.add(:sales,
101
+ :sale_is_out_of_time_frame,
102
+ slug: sale.slug.blank? ? "##{sale.id}" : sale.slug,
103
+ ends_at_time: I18n.l(sale.ends_at))
104
+ end
105
+ end
72
106
  end
73
107
  end
74
108
 
@@ -76,22 +110,23 @@ end
76
110
  #
77
111
  # Table name: auctify_sales_packs
78
112
  #
79
- # id :bigint(8) not null, primary key
80
- # title :string
81
- # description :text
82
- # position :integer default(0)
83
- # slug :string
84
- # place :string
85
- # published :boolean default(FALSE)
86
- # created_at :datetime not null
87
- # updated_at :datetime not null
88
- # sales_count :integer default(0)
89
- # start_date :date
90
- # end_date :date
91
- # sales_interval :integer default(3)
92
- # sales_beginning_hour :integer default(20)
93
- # sales_beginning_minutes :integer default(0)
94
- # commission_in_percent :integer
113
+ # id :bigint(8) not null, primary key
114
+ # title :string
115
+ # description :text
116
+ # position :integer default(0)
117
+ # slug :string
118
+ # place :string
119
+ # published :boolean default(FALSE)
120
+ # created_at :datetime not null
121
+ # updated_at :datetime not null
122
+ # sales_count :integer default(0)
123
+ # start_date :date
124
+ # end_date :date
125
+ # sales_interval :integer default(3)
126
+ # sales_beginning_hour :integer default(20)
127
+ # sales_beginning_minutes :integer default(0)
128
+ # commission_in_percent :integer
129
+ # auction_prolonging_limit_in_seconds :integer
95
130
  #
96
131
  # Indexes
97
132
  #
@@ -57,6 +57,7 @@ module Auctify
57
57
 
58
58
  def approved_bid?
59
59
  @approved_bid ||= begin
60
+ bid.valid?
60
61
  check_bidder
61
62
  changing_own_limit? ? check_max_price_increasing : check_price_minimum
62
63
  check_same_bidder
@@ -114,7 +115,7 @@ module Auctify
114
115
  end
115
116
 
116
117
  def first_bid?
117
- bids.empty?
118
+ winning_bid.nil?
118
119
  end
119
120
 
120
121
  def bids
@@ -201,10 +202,11 @@ module Auctify
201
202
 
202
203
  @updated_win_bid = winning_bid.dup
203
204
  @updated_win_bid.price = [price, winning_bid.max_price].min
205
+ @updated_win_bid.autobid = true
204
206
  end
205
207
 
206
208
  def increase_price(price)
207
- return price + 1 if bid_steps_ladder.blank?
209
+ return price + Auctify.configuration.require_bids_to_be_rounded_to if bid_steps_ladder.blank?
208
210
 
209
211
  _range, increase_step = bid_steps_ladder.detect { |range, step| range.cover?(price) }
210
212
  price + increase_step
@@ -34,14 +34,17 @@ cs:
34
34
  published: Zveřejněno
35
35
  sales_count: Počet položek
36
36
  sales: Položky
37
+ items: Předměty
37
38
  sales_interval: Časový rozestup mezi předměty v minutách
38
39
  sales_beginning_hour: Čas prvního předmětu (hodina)
39
40
  sales_beginning_minutes: Čas prvního předmětu (minuty)
40
41
  commission_in_percent: Provize aukční síně (procenta)
42
+ auction_prolonging_limit_in_seconds: Časový limit pro prodloužení aukce (sekundy)
41
43
  auctify/sale/base:
42
44
  buyer: Kupec
43
45
  seller: Prodejce
44
- item: Zboží
46
+ item: Předmět
47
+ pack: Prodejní balík
45
48
  aasm_state: Stav
46
49
  published: Zveřejněno
47
50
  number: Číslo
@@ -58,14 +61,25 @@ cs:
58
61
  updated_at: Změněna
59
62
  reserve_price: Rezervovaná cena
60
63
  ends_at: Předpokládaný konec
61
- pack_id: Aukce+
62
- pack: Aukce-
64
+ pack_id: Aukce
65
+ pack: Aukce
66
+ bidder_registrations: Registrace do aukce položky
67
+ bidders: Dražitelé
68
+ bids: Příhozy
69
+ winner: Vítěz dražby
70
+ current_winner: Aktuální výherce
63
71
  auctify/bid:
64
72
  price: Výše příhozu
65
- max_price: Maximální výše příhozu
73
+ max_price: Limit příhozů
66
74
  created_at: Přihozeno
67
75
  updated_at:
68
76
  registration: Registrace do aukce položky
77
+ auction: Položka aukce
78
+ bidder: Dražitel
79
+ auctify/bidder_registration:
80
+ bidder: Dražitel
81
+ auction: Položka aukce
82
+ bids: Příhozy
69
83
 
70
84
  errors:
71
85
  models:
@@ -98,6 +112,8 @@ cs:
98
112
  attributes:
99
113
  end_date:
100
114
  smaller_than_start_date: "musí být později než začátek"
115
+ sales:
116
+ sale_is_out_of_time_frame: "Položka '%{slug}' má čas konce (%{ends_at_time}) mimo rámec aukce"
101
117
  auctify/bid:
102
118
  not_confirmed: "Příhoz je potřeba potvrdit"
103
119
  attributes:
@@ -113,8 +129,10 @@ cs:
113
129
  price_is_bellow_opening_price: "je nižší než vyvolávací cena"
114
130
  price_is_bellow_minimal_bid: "je nižší než aktuální minimální příhoz %{minimal_bid}"
115
131
  must_be_lower_or_equal_max_price: "musí být nižší nebo rovna maximálnímu limitu"
132
+ must_be_rounded_to: "musí být zaokrouhlená na celé %{round_to} Kč"
116
133
  max_price:
117
134
  price_is_bellow_minimal_bid: "je nižší než aktuální minimální příhoz %{minimal_bid}"
135
+ must_be_rounded_to: "musí být zaokrouhlená na celé %{round_to} Kč"
118
136
  auctify/bidder_registration:
119
137
  attributes:
120
138
  auction:
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ChangeCommissionColumns < ActiveRecord::Migration[6.1]
4
+ def change
5
+ rename_column :auctify_sales, :commission_in_percent, :seller_commission_in_percent
6
+ add_column :auctify_sales, :buyer_commission_in_percent, :integer
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddAuctionProlongingLimitToSalesPacks < ActiveRecord::Migration[6.1]
4
+ def change
5
+ add_column :auctify_sales_packs, :auction_prolonging_limit_in_seconds, :integer
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddAutobidToAuctifyBids < ActiveRecord::Migration[6.1]
4
+ def up
5
+ add_column :auctify_bids, :autobid, :boolean, default: false
6
+ puts("RUN `Auctify::AutobidFillerJob.perform_later`")
7
+ end
8
+
9
+ def down
10
+ remove_column :auctify_bids, :autobid
11
+ end
12
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UpdateSalesFeaturable < ActiveRecord::Migration[6.1]
4
+ def up
5
+ rename_column :auctify_sales, :featured, :featured_tmp_boolean
6
+
7
+ add_column :auctify_sales, :featured, :integer, default: nil
8
+ add_index :auctify_sales, :featured
9
+
10
+ execute("UPDATE auctify_sales SET featured = 1 WHERE featured_tmp_boolean = TRUE;")
11
+
12
+ remove_column :auctify_sales, :featured_tmp_boolean
13
+ end
14
+
15
+ def down
16
+ rename_column :auctify_sales, :featured, :featured_tmp_integer
17
+
18
+ add_column :auctify_sales, :featured, :boolean, default: false
19
+ add_index :auctify_sales, :featured
20
+
21
+ execute("UPDATE auctify_sales SET featured = TRUE WHERE featured_tmp_integer IS NOT NULL;")
22
+
23
+ remove_column :auctify_sales, :featured_tmp_integer
24
+ end
25
+ end
@@ -3,23 +3,27 @@
3
3
  module Auctify
4
4
  class Configuration
5
5
  attr_accessor :autoregister_as_bidders_all_instances_of_classes,
6
- :auction_prolonging_limit,
6
+ :auction_prolonging_limit_in_seconds,
7
7
  :auctioneer_commission_in_percent,
8
8
  :autofinish_auction_after_bidding,
9
9
  :when_to_notify_bidders_before_end_of_bidding,
10
10
  :default_bid_steps_ladder,
11
- :restrict_overbidding_yourself_to_max_price_increasing
11
+ :restrict_overbidding_yourself_to_max_price_increasing,
12
+ :require_bids_to_be_rounded_to,
13
+ :allow_changes_on_auction_with_bids_for_attributes
12
14
 
13
15
 
14
16
  def initialize
15
17
  # set defaults here
16
18
  @autoregister_as_bidders_all_instances_of_classes = []
17
- @auction_prolonging_limit = 2.minutes
19
+ @auction_prolonging_limit_in_seconds = 2.minutes
18
20
  @auctioneer_commission_in_percent = 1 # %
19
21
  @autofinish_auction_after_bidding = false
20
22
  @when_to_notify_bidders_before_end_of_bidding = nil # no notifying
21
23
  @default_bid_steps_ladder = { 0.. => 1 }
22
24
  @restrict_overbidding_yourself_to_max_price_increasing = true
25
+ @require_bids_to_be_rounded_to = 1
26
+ @allow_changes_on_auction_with_bids_for_attributes = []
23
27
  end
24
28
 
25
29
  def autoregistering_for?(instance)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "aasm"
4
+ require "with_advisory_lock"
4
5
  require_relative "../../app/models/auctify/behaviors"
5
6
 
6
7
  module Auctify
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Auctify
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: auctify
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - foton
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-28 00:00:00.000000000 Z
11
+ date: 2022-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,20 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 6.0.3
20
- - - ">="
21
- - !ruby/object:Gem::Version
22
- version: 6.0.3.7
19
+ version: 6.1.4
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
24
  - - "~>"
28
25
  - !ruby/object:Gem::Version
29
- version: 6.0.3
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: 6.0.3.7
26
+ version: 6.1.4
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: aasm
35
29
  requirement: !ruby/object:Gem::Requirement
@@ -58,6 +52,20 @@ dependencies:
58
52
  - - ">="
59
53
  - !ruby/object:Gem::Version
60
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: with_advisory_lock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
61
69
  - !ruby/object:Gem::Dependency
62
70
  name: pg
63
71
  requirement: !ruby/object:Gem::Requirement
@@ -272,16 +280,16 @@ dependencies:
272
280
  name: selenium-webdriver
273
281
  requirement: !ruby/object:Gem::Requirement
274
282
  requirements:
275
- - - ">="
283
+ - - "~>"
276
284
  - !ruby/object:Gem::Version
277
- version: '0'
285
+ version: 3.142.7
278
286
  type: :development
279
287
  prerelease: false
280
288
  version_requirements: !ruby/object:Gem::Requirement
281
289
  requirements:
282
- - - ">="
290
+ - - "~>"
283
291
  - !ruby/object:Gem::Version
284
- version: '0'
292
+ version: 3.142.7
285
293
  - !ruby/object:Gem::Dependency
286
294
  name: webdrivers
287
295
  requirement: !ruby/object:Gem::Requirement
@@ -335,8 +343,10 @@ files:
335
343
  - app/helpers/auctify/sales_helper.rb
336
344
  - app/helpers/auctify/sales_packs_helper.rb
337
345
  - app/jobs/auctify/application_job.rb
346
+ - app/jobs/auctify/autobid_filler_job.rb
338
347
  - app/jobs/auctify/bidding_closer_job.rb
339
348
  - app/jobs/auctify/bidding_is_close_to_end_notifier_job.rb
349
+ - app/jobs/auctify/ensure_auctions_closing_job.rb
340
350
  - app/mailers/auctify/application_mailer.rb
341
351
  - app/models/auctify/application_record.rb
342
352
  - app/models/auctify/behaviors.rb
@@ -411,6 +421,10 @@ files:
411
421
  - db/migrate/20210607113440_add_commission_in_percent_to_sales_packs.rb
412
422
  - db/migrate/20210617062509_add_index_to_auctify_sales_currently_ends_at.rb
413
423
  - db/migrate/20210625125732_add_current_winner_to_auctify_sales.rb
424
+ - db/migrate/20210825155543_change_commission_columns.rb
425
+ - db/migrate/20210917082313_add_auction_prolonging_limit_to_sales_packs.rb
426
+ - db/migrate/20211022133253_add_autobid_to_auctify_bids.rb
427
+ - db/migrate/20211203064934_update_sales_featurable.rb
414
428
  - lib/auctify.rb
415
429
  - lib/auctify/configuration.rb
416
430
  - lib/auctify/engine.rb
@@ -420,7 +434,7 @@ homepage: https://github.com/sinfin/auctify
420
434
  licenses:
421
435
  - MIT
422
436
  metadata: {}
423
- post_install_message:
437
+ post_install_message:
424
438
  rdoc_options: []
425
439
  require_paths:
426
440
  - lib
@@ -435,8 +449,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
435
449
  - !ruby/object:Gem::Version
436
450
  version: '0'
437
451
  requirements: []
438
- rubygems_version: 3.2.11
439
- signing_key:
452
+ rubygems_version: 3.2.28
453
+ signing_key:
440
454
  specification_version: 4
441
455
  summary: Gem for adding auction behavior to models.
442
456
  test_files: []