Class: HhPackage::ReservationPackages::ChargeCalculator

Inherits:
Object
  • Object
show all
Includes:
ElasticAPM::SpanHelpers
Defined in:
app/my_lib/hh_package/reservation_packages/charge_calculator.rb

Overview

Calculates charges for package reservations with comprehensive pricing logic. Handles delivery fees, vouchers, loyalty points, and DIY package spending tiers.

This calculator processes both fixed and relative charge packages, applies promotional pricing like “Come more pay less”, and handles add-on pricing. For DIY packages, it extracts spending tier information and includes it as a separate attribute in the response rather than embedding it within each package.

See Also:

  • for comprehensive documentation

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeChargeCalculator

Returns a new instance of ChargeCalculator.



19
20
21
22
23
24
25
26
27
28
29
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 19

def initialize
  @price_finder = HhPackage::ReservationPackages::PriceFinder.new
  @vouchers = []
  @delivery_pricing_tiers = []
  @subsidize_price = HhMoney.new(0, DEFAULT_CURRENCY)
  @user_id = nil
  @custom_price_cents = nil
  @use_dynamic_pricing = false
  @accept_refund = false
  @custom_refund_fee_cents = 0
end

Instance Attribute Details

#price_finderObject (readonly)

Returns the value of attribute price_finder.



17
18
19
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 17

def price_finder
  @price_finder
end

#subsidize_priceObject (readonly)

Returns the value of attribute subsidize_price.



17
18
19
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 17

def subsidize_price
  @subsidize_price
end

#user_idObject (readonly)

Returns the value of attribute user_id.



17
18
19
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 17

def user_id
  @user_id
end

#vouchersObject (readonly)

Returns the value of attribute vouchers.



17
18
19
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 17

def vouchers
  @vouchers
end

Instance Method Details

#accept_refund=(value) ⇒ Object



62
63
64
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 62

def accept_refund=(value)
  @accept_refund = value
end

#accept_refund?Boolean

Returns:

  • (Boolean)


58
59
60
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 58

def accept_refund?
  @accept_refund
end

#assign_voucher(vouchers) ⇒ Object



50
51
52
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 50

def assign_voucher(vouchers)
  @vouchers = Array.wrap(vouchers)
end

#calculate(package_bought, adult, kids, distance = 0.0, restaurant = nil, reservation_date = nil, add_on_bought = nil) ⇒ Object

Returns

:charge_amount_type=>:relative,
:charge_percent=>100,
:charge_price=>10, # in baht
:currency=>"THB",
:total_price=>10 # in baht

.

Parameters:

  • package_bought (Hash)

    keys:

    package: [HhPackage::Package::Ayce etc]
    quantity: [Integer]
    
  • adult (Integer)
  • kids (Integer)
  • distance (Float) (defaults to: 0.0)
  • restaurant (Restaurant) (defaults to: nil)
  • reservation_date (Date) (defaults to: nil)

Returns:

  • :charge_amount_type=>:relative,
    :charge_percent=>100,
    :charge_price=>10, # in baht
    :currency=>"THB",
    :total_price=>10 # in baht
    



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 176

def calculate(
  package_bought, adult, kids, distance = 0.0, restaurant = nil, reservation_date = nil, add_on_bought = nil
)
  # Convert string inputs to integers
  adult = adult.to_i if adult.is_a?(String)
  kids = kids.to_i if kids.is_a?(String)

  # Set minimal business context (only restaurant_id to avoid duplicating data in all logs)
  # BUSINESS_LOGGER.set_business_context({
  #                                        restaurant_id: restaurant&.id,
  #                                        user_id: @user_id,
  #                                      })

  # BUSINESS_LOGGER.info('ChargeCalculator.calculate started', {
  #                        input: {
  #                          adult: adult,
  #                          kids: kids,
  #                          distance: distance,
  #                          reservation_date: reservation_date,
  #                          package_count: package_bought&.length || 0,
  #                          add_on_count: add_on_bought&.length || 0,
  #                          package_types: package_bought&.map { |p| p[:package]&.class&.name },
  #                          add_on_ids: add_on_bought&.map { |a| a[:add_on]&.id },
  #                          accept_refund: @accept_refund,
  #                          custom_refund_fee_cents: @custom_refund_fee_cents,
  #                          has_vouchers: @vouchers.present?,
  #                          use_dynamic_pricing: @use_dynamic_pricing,
  #                        },
  #                      })

  # Separate packages into relative and fixed
  relative_packages = []
  fixed_packages = []

  package_bought.each do |item|
    charge_amount_type = item[:package].charge_amount_type.to_sym
    case charge_amount_type
    when :relative
      relative_packages.push(item)
    when :fixed, :flat, :per_person
      # fixed is per person (for all package type except pp), per pack (for party pack)
      # flat is per booking
      # per_person is per person (party pack only)
      fixed_packages.push(item)
    else
      raise NotImplementedError
    end
  end

  # Calculate results for relative and fixed packages separately
  result = []
  if relative_packages.present?
    result += calculate_for_packages(package_bought, relative_packages, :relative, adult, kids, reservation_date,
                                     distance, restaurant)
  end
  if fixed_packages.present?
    result += calculate_for_packages(package_bought, fixed_packages, :fixed, adult, kids, reservation_date,
                                     distance, restaurant)
  end

  # Calculate add-on price if any
  add_on_calculation_result = calculate_for_add_ons(add_on_bought, adult, kids, restaurant)
  result += [add_on_calculation_result]

  aggregated_result = result.each_with_object({}) do |hash, aggregated|
    hash.each do |key, value|
      case value
      when Numeric
        if key == :charge_percent
          # Get the highest charge percent among all packages
          aggregated[key] = [aggregated[key] || 0, value].max
          next
        end

        # sum up the numeric value
        aggregated[key] = (aggregated[key] || 0) + value
      when Array
        # concat the array, if the key is already exist
        aggregated[key] ||= []
        aggregated[key] += value
      when String, Symbol, TrueClass, FalseClass
        # assign the value if it's not numeric, array, or hash
        aggregated[key] ||= value
      else
        aggregated[key] = aggregated[key] || value
      end
    end
  end

  # Re-apply vouchers to the combined total (packages + add-ons)
  # This ensures vouchers deduct from the full order total, not just package prices
  recalculate_with_vouchers(aggregated_result, distance)

  # Log final calculation results
  # BUSINESS_LOGGER.info('ChargeCalculator.calculate completed', {
  #                        output: {
  #                          total_price_cents: aggregated_result[:total_price_cents],
  #                          charge_price_cents: aggregated_result[:charge_price_cents],
  #                          total_refund_price_cents: aggregated_result[:total_refund_price_cents],
  #                          total_refundable_amount_cents: aggregated_result[:total_refundable_amount_cents],
  #                          delivery_fee: aggregated_result[:delivery_fee],
  #                          voucher_amount_hh: aggregated_result[:used_voucher_amount_by_hh],
  #                          voucher_amount_restaurant: aggregated_result[:used_voucher_amount_by_restaurant],
  #                          spending_tier_discount_cents: aggregated_result[:spending_tier_discount_cents],
  #                        },
  #                      })

  aggregated_result
end

#calculate_by_distance(distance, restaurant) ⇒ Object

returns 0 if restaurant package price is >= restaurant delivery pricing tier minimum value

Parameters:

Returns:

  • total amount in baht [Integer]



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 78

def calculate_by_distance(distance, restaurant)
  return decide_delivery_fee(0, distance) if restaurant.delivery_pricing_tiers.blank?

  cache_key = "calculate_by_distance:#{restaurant.id}:#{restaurant.restaurant_packages.cache_key}:#{restaurant.delivery_pricing_tiers.cache_key}"

  delivery_tier = Rails.cache.fetch("#{cache_key}:delivery_tier", expires_in: 10.minutes) do
    restaurant.delivery_pricing_tiers.min_by(&:start_rate_price)
  rescue StandardError
    :nil
  end

  return decide_delivery_fee(0, distance) if delivery_tier == :nil

  package_min_price = Rails.cache.fetch("#{cache_key}:package_min_price", expires_in: 10.minutes) do
    restaurant_package = restaurant.restaurant_packages.select do |rp|
      if rp.package.respond_to?(:is_add_on)
        rp.package.is_add_on? == false
      else
        true
      end
    end
    if restaurant_package.present?
      restaurant_package.min_by do |rp|
        rp.package.lowest_pricing.price
      end.package.lowest_pricing.price
    else
      # add default pricing if no package found
      HhPackage::Pricing.new.price
    end
  rescue StandardError => e
    APMErrorHandler.report(e, restaurant_id: restaurant.id, distance: distance)
    :nil
  end

  return decide_delivery_fee(0, distance) if package_min_price == :nil

  free_distance = if package_min_price >= delivery_tier.start_rate_price
                    delivery_tier.free_delivery_radius_in_km.to_d
                  else
                    0.0
                  end

  distance = distance.to_d

  if free_distance.present?
    return 0 if distance <= free_distance

    has_to_pay = decide_delivery_fee(0, free_distance)
    covered = decide_delivery_fee(0, distance)
    return covered - has_to_pay
  end

  decide_delivery_fee(0, distance)
end

#calculate_for_add_ons(add_on_bought, adult, kids, restaurant) ⇒ Object



520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 520

def calculate_for_add_ons(add_on_bought, adult, kids, restaurant)
  adult = 0 if adult.blank?
  kids = 0 if kids.blank?
  price_currency = restaurant.default_currency || DEFAULT_CURRENCY

  res = {
    total_price: 0,
    total_price_cents: 0,
    total_price_v2: 0,
    charge_price: 0,
    charge_price_cents: 0,
    charge_price_v2: 0,
    add_on_currency: '',
    add_on_total_price: 0,
    add_on_total_price_v2: 0,
    selected_add_ons: [],
    total_point_earn: 0,
    total_refund_price: 0,
    total_refund_price_cents: 0,
    total_refund_price_v2: 0,
    total_refundable_amount: 0,
    total_refundable_amount_cents: 0,
    total_refundable_amount_v2: 0,
  }
  return res if add_on_bought.blank?

  # Calculate total refund price for add-ons (if any)
  # NOTE: If custom_refund_fee_cents is provided, refund fee is already applied to packages
  # So we skip calculating add-on refund fees to avoid duplication
  if accept_refund? && custom_refund_fee_cents.zero?
    total_refund_price_result = find_add_ons_total_refund_price(add_on_bought, adult, kids, price_currency)
    total_refund_price_cents = total_refund_price_result.amount_cents
    res[:total_refund_price] = total_refund_price_result.amount.ceil
    res[:total_refund_price_cents] = total_refund_price_cents
    res[:total_refund_price_v2] =
      total_refund_price_result.amount % 1 == 0 ? total_refund_price_result.amount.to_i : total_refund_price_result.amount.to_f.round(2)
  else
    total_refund_price_cents = 0
  end

  selected_data_add_ons = []
  add_on_bought.each do |item|
    add_on = item[:add_on]
    quantity = item[:quantity] || 0

    next if add_on.blank?

    total_add_on_price_cents = price_finder.find_add_on_final_price(
      add_on, quantity, adult, kids
    )
    price_currency = add_on.pricing.price_currency
    add_on_total_price = HhMoney.new(total_add_on_price_cents, price_currency).amount.ceil
    add_on_total_price_cents = HhMoney.new(total_add_on_price_cents, price_currency).cents
    add_on_total_price_v2 = HhMoney.new(add_on_total_price_cents, price_currency).amount
    add_on_total_price_v2 = if add_on_total_price_v2 % 1 == 0
                              add_on_total_price_v2.to_i
                            else
                              add_on_total_price_v2.to_f.round(2)
                            end

    selected_add_ons(
      add_on_bought, item[:restaurant_add_on_id], adult, kids, total_add_on_price_cents
    ).each do |selected_package|
      selected_data_add_ons << selected_package
    end

    # Calculate estimation point for add-on
    estimation_point = calculate_point_for_add_on(add_on_total_price, restaurant)

    # Add refundable amount for add-ons
    if accept_refund?
      res[:total_refundable_amount] += add_on_total_price
      res[:total_refundable_amount_cents] += add_on_total_price_cents
      res[:total_refundable_amount_v2] += add_on_total_price_v2
    end

    res[:total_price] += add_on_total_price
    res[:total_price_cents] += add_on_total_price_cents
    res[:total_price_v2] += add_on_total_price_v2
    res[:charge_price] += add_on_total_price
    res[:charge_price_cents] += add_on_total_price_cents
    res[:charge_price_v2] += add_on_total_price_v2
    res[:add_on_total_price] += add_on_total_price
    res[:add_on_total_price_v2] += add_on_total_price_v2
    res[:add_on_currency] = price_currency
    res[:selected_add_ons] = selected_data_add_ons
    res[:total_point_earn] += estimation_point
  end

  # Add refund price to total and charge prices
  res[:total_price] += res[:total_refund_price]
  res[:total_price_cents] += total_refund_price_cents
  res[:total_price_v2] += res[:total_refund_price_v2]
  res[:charge_price] += res[:total_refund_price]
  res[:charge_price_cents] += total_refund_price_cents
  res[:charge_price_v2] += res[:total_refund_price_v2]

  res
end

#calculate_for_packages(package_bought, packages, type, adult, kids, reservation_date, distance, restaurant) ⇒ Object



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 287

def calculate_for_packages(package_bought, packages, type, adult, kids, reservation_date, distance, restaurant)
  # Calculate for each package and collect results
  package_results = packages.map do |item|
    package = item[:package]
    quantity = item[:quantity]
    is_party_pack = package.package_attr.package_type == 'HhPackage::Package::PartyPack'

    if package.ala_carte? && item[:menu_sections].present?
      price_finder.menu_sections = filter_menu_sections(package, item[:menu_sections]).map(&:to_h)
    end

    # Handle group_sections
    # currently used for DIY packages only
    if item[:group_sections].present?
      price_finder.group_sections = filter_group_sections(package, item[:group_sections])
    end

    total_package_price_cents = price_finder.find_package_final_price(package, quantity, adult, kids, reservation_date,
                                                                      custom_price_cents: @custom_price_cents, packages: packages)
    price_currency = price_finder.find_base_price(package, adult, reservation_date).price_currency

    # Calculate charge price based on package type
    case type.to_sym
    when :fixed
      # Calculate charge price based on fixed charge amount
      # When refund guarantee is enabled, charge full price (100%) regardless of fixed charge amount
      charge_price = if require_full_prepayment? || accept_refund?
                       HhMoney.new((total_package_price_cents * quantity), price_currency)
                     elsif package.charge_amount_type.to_sym == :flat
                       # flat charge is per booking, no need to multiply by quantity
                       HhMoney.new(package.fixed_charge_amount_cents.to_d.ceil, price_currency)
                     elsif package.charge_amount_type.to_sym == :per_person && is_party_pack
                       # per_person charge is per person, multiply by adult, for party pack only
                       HhMoney.new((package.fixed_charge_amount_cents.to_d.ceil * adult), price_currency)
                     elsif is_party_pack && package.package_attr&.comemore_payless?
                       # for party pack with come more pay less
                       HhMoney.new(total_package_price_cents, price_currency)
                     else
                       HhMoney.new((package.fixed_charge_amount_cents.to_d.ceil * quantity), price_currency)
                     end
      charge_percent = 100
    when :relative
      # Calculate charge price based on relative charge percent
      relative_charge_percent = calc_charge_percent(package.relative_charge_percent.to_d.ceil, package_bought)
      charge_price = HhMoney.new((total_package_price_cents * relative_charge_percent / 100), price_currency)
      charge_percent = relative_charge_percent
    else
      raise NotImplementedError, "type #{type} not implemented"
    end

    # Calculate total package price
    total_package_price = HhMoney.new(total_package_price_cents, price_currency)
    total_package_price_amount = total_package_price.amount.ceil
    total_package_price_v2_amount = total_package_price.amount % 1 == 0 ? total_package_price.amount.to_i : total_package_price.amount.to_f.round(2)

    # Calculate total refund price (if any)
    # NOTE: custom_refund_fee_cents is per-booking, not per-package
    # So we don't add it here in the per-package loop to avoid counting it multiple times
    if accept_refund? && custom_refund_fee_cents.zero?
      # Calculate refund fee based on package rules
      total_package_refund_cents = price_finder.find_package_refund_final_price(
        package, total_package_price_cents
      )
      total_refund_price_result = HhMoney.new(total_package_refund_cents, price_currency)
      total_refund_price = total_refund_price_result.amount.ceil
      total_refund_price_cents = total_refund_price_result.amount_cents
      total_refund_price_v2_amount = total_refund_price_result.amount % 1 == 0 ? total_refund_price_result.amount.to_i : total_refund_price_result.amount.to_f.round(2)
    else
      total_refund_price = 0
      total_refund_price_cents = 0
      total_refund_price_v2_amount = 0
    end

    # Store raw prices (without vouchers) - vouchers will be applied after aggregating packages + add-ons
    final_total_price = total_package_price_amount + total_refund_price
    final_total_price_cents = total_package_price.amount_cents + total_refund_price_cents

    final_total_price_v2 = HhMoney.new(final_total_price_cents, price_currency).amount
    final_total_price_v2 = if final_total_price_v2 % 1 == 0
                             final_total_price_v2.to_i
                           else
                             final_total_price_v2.to_f.round(2)
                           end

    # Store charge price without vouchers (vouchers will be applied after aggregation)
    final_charge_price = charge_price.amount.ceil + total_refund_price
    final_charge_price_cents = charge_price.amount_cents + total_refund_price_cents

    final_charge_price_v2 = HhMoney.new(final_charge_price_cents, price_currency).amount
    final_charge_price_v2 = if final_charge_price_v2 % 1 == 0
                              final_charge_price_v2.to_i
                            else
                              final_charge_price_v2.to_f.round(2)
                            end

    # Calculate estimation point for package (exclude refund price from point calculation)
    total_price_for_points = final_total_price - total_refund_price
    estimation_point = calculate_point_for_package(total_price_for_points, package, restaurant, reservation_date)

    # Calculate delivery fee (before vouchers)
    delivery_fee = decide_delivery_fee(final_total_price, distance, package)

    # Find selected packages details
    selected_packages_result = selected_packages(
      packages, item[:restaurant_package_id], adult, kids, total_package_price_cents, reservation_date
    )
    selected_packages_data = selected_packages_result[:packages]

    # Define total refundable amount attributes
    if accept_refund?
      total_refundable_amount = total_package_price_amount
      total_refundable_amount_cents = total_package_price.amount_cents
      total_refundable_amount_v2 = total_package_price_v2_amount
    else
      total_refundable_amount = 0
      total_refundable_amount_cents = 0
      total_refundable_amount_v2 = 0
    end

    {
      currency: price_currency.to_s || DEFAULT_CURRENCY,
      total_price: final_total_price.ceil,
      total_price_cents: final_total_price_cents,
      total_price_v2: final_total_price_v2,
      total_refund_price: total_refund_price,
      total_refund_price_cents: total_refund_price_cents,
      total_refund_price_v2: total_refund_price_v2_amount,
      total_refundable_amount: total_refundable_amount,
      total_refundable_amount_cents: total_refundable_amount_cents,
      total_refundable_amount_v2: total_refundable_amount_v2,
      charge_price: final_charge_price.ceil,
      charge_price_cents: final_charge_price_cents,
      charge_price_v2: final_charge_price_v2,
      charge_amount_type: package.charge_amount_type,
      charge_percent: charge_percent,
      charge_type: determine_charge_type(package),
      is_dine_in: !package.respond_to?(:delivery_fee),
      selected_packages: selected_packages_data,
      total_package_price: total_package_price_amount,
      total_package_price_v2: total_package_price_v2_amount,
      total_package_price_cents: total_package_price.amount_cents,
      total_point_earn: estimation_point,
      delivery_fee: delivery_fee.to_d.ceil,
      original_delivery_fee: delivery_fee.to_d.ceil,
      delivery_fee_currency: get_delivery_fee_currency(package, final_total_price_v2),
      delivery_fee_in_baht: get_delivery_fee_in_baht(package),
      delivery_fee_per_km_in_baht: get_delivery_fee_per_km_in_baht(package, final_total_price_v2),
      free_delivery_fee_threshold_in_baht: get_free_delivery_fee_threshold_in_baht(
        package, final_total_price_v2
      ),
      delivery_radius: get_free_delivery_radius_threshold(package, final_total_price_v2),
    }
  end

  # Aggregate all package results
  aggregated_result = package_results.each_with_object({}) do |hash, aggregated|
    hash.each do |key, value|
      case value
      when Numeric
        if key == :charge_percent
          # Get the highest charge percent among all packages
          aggregated[key] = [aggregated[key] || 0, value].max
          next
        end

        # sum up the numeric value
        aggregated[key] = (aggregated[key] || 0) + value
      when Array
        # concat the array, if the key is already exist
        aggregated[key] ||= []
        aggregated[key] += value
      when String, Symbol, TrueClass, FalseClass
        # assign the value if it's not numeric, array, or hash
        aggregated[key] ||= value
      else
        aggregated[key] = aggregated[key] || value
      end
    end
  end

  # Apply spending tier discount for DIY packages (only applies to packages, not add-ons)
  apply_spending_tier_discount(aggregated_result, packages, aggregated_result[:currency], adult)

  # Validate minimum spending after all calculations
  validation_error = HhPackage::ReservationPackages::Validator.validate_minimum_spending(
    packages,
    aggregated_result[:total_package_price] || 0,
    adult,
  )

  if validation_error.present?
    aggregated_result[:validation_error] = validation_error
  end

  # Apply custom refund fee if provided (admin override)
  # This is a booking-level fee, not per-package, so we apply it once after aggregation
  if accept_refund? && custom_refund_fee_cents > 0
    currency = aggregated_result[:currency]

    custom_refund_fee_money = HhMoney.new(custom_refund_fee_cents, currency)
    custom_refund_fee_amount = custom_refund_fee_money.amount.ceil
    custom_refund_fee_amount_v2 = if custom_refund_fee_amount % 1 == 0
                                    custom_refund_fee_amount.to_i
                                  else
                                    custom_refund_fee_amount.to_f.round(2)
                                  end

    # Update refund fee fields
    aggregated_result[:total_refund_price_cents] = custom_refund_fee_cents
    aggregated_result[:total_refund_price] = custom_refund_fee_amount
    aggregated_result[:total_refund_price_v2] = custom_refund_fee_amount_v2

    # Adjust total and charge prices by the difference
    aggregated_result[:total_price_cents] += custom_refund_fee_cents
    aggregated_result[:charge_price_cents] += custom_refund_fee_cents

    # Recalculate amount fields from cents
    total_price_result = HhMoney.new(aggregated_result[:total_price_cents], currency)
    aggregated_result[:total_price] = total_price_result.amount.ceil
    aggregated_result[:total_price_v2] =
      total_price_result.amount % 1 == 0 ? total_price_result.amount.to_i : total_price_result.amount.to_f.round(2)

    charge_price_result = HhMoney.new(aggregated_result[:charge_price_cents], currency)
    aggregated_result[:charge_price] = charge_price_result.amount.ceil
    aggregated_result[:charge_price_v2] =
      charge_price_result.amount % 1 == 0 ? charge_price_result.amount.to_i : charge_price_result.amount.to_f.round(2)
  end

  # Return as an array with the aggregated result
  [aggregated_result]
end

#custom_refund_fee_centsObject



66
67
68
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 66

def custom_refund_fee_cents
  @custom_refund_fee_cents
end

#custom_refund_fee_cents=(value) ⇒ Object



70
71
72
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 70

def custom_refund_fee_cents=(value)
  @custom_refund_fee_cents = value
end

#rough_calculation(distance, restaurant) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 133

def rough_calculation(distance, restaurant)
  distance = distance.to_d
  return 0 unless distance > 0.0

  delivery_tier = Rails.cache.fetch("#{self.class}:rough_calculation:#{restaurant.id}", expires_in: 10.minutes) do
    cheapest_delivery_pricing_tier = restaurant.delivery_pricing_tiers.order('start_rate_price_cents DESC').first

    cheapest_delivery_pricing_tier.presence ||
      DeliveryPricingTier.global_scope.order('start_rate_price_cents DESC').first
  end

  free_delivery_radius_threshold = delivery_tier.free_delivery_radius_in_km
  delivery_fee_per_km_in_baht = delivery_tier.delivery_fee_price
  delivery_fee_currency = delivery_tier.price_currency

  has_to_pay_more = distance > free_delivery_radius_threshold.to_d

  if has_to_pay_more
    additional_fee = (distance - free_delivery_radius_threshold).ceil * delivery_fee_per_km_in_baht.to_d
    additional_fee = HhMoney.new(additional_fee * 100, delivery_fee_currency)
    return additional_fee.amount
  end

  0
end

#set_custom_price_cents(package) ⇒ Object



44
45
46
47
48
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 44

def set_custom_price_cents(package)
  if package.present? && package[:custom_package_data]&.present?
    @custom_price_cents = package[:custom_package_data][0][:custom_price_cents]
  end
end

#set_delivery_pricing_tiers(delivery_pricing_tiers) ⇒ Object



54
55
56
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 54

def set_delivery_pricing_tiers(delivery_pricing_tiers)
  @delivery_pricing_tiers = delivery_pricing_tiers
end

#set_user(user_id) ⇒ Object



40
41
42
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 40

def set_user(user_id)
  @user_id = user_id
end

#use_dynamic_pricing=(value) ⇒ Object



35
36
37
38
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 35

def use_dynamic_pricing=(value)
  @use_dynamic_pricing = value
  @price_finder.use_dynamic_pricing = value
end

#use_dynamic_pricing?Boolean

Returns:

  • (Boolean)


31
32
33
# File 'app/my_lib/hh_package/reservation_packages/charge_calculator.rb', line 31

def use_dynamic_pricing?
  @use_dynamic_pricing
end