Class: NotificationWorkers::ReservationReport
- Inherits:
-
ApplicationWorker
- Object
- ApplicationWorker
- NotificationWorkers::ReservationReport
- Includes:
- MoneyRails::ActionViewExtension, ReservationExportHelpers, Sidekiq::Status::Worker
- Defined in:
- app/workers/notification_workers/reservation_report.rb
Constant Summary collapse
- CHANNEL_IDS_TO_SKIP =
[435].freeze
- BATCH_SIZE =
Memory management constants
1000- GC_INTERVAL =
Force GC every 100 reservations
100- MEMORY_CLEANUP_THRESHOLD =
Force GC if processing more than 50k reservations
10_000
Constants included from ExportConstants
ExportConstants::DEFAULT_QUEUE, ExportConstants::EMAIL_BATCH_SIZE, ExportConstants::ERROR_CATEGORIES, ExportConstants::EXCEL_FILE_EXTENSION, ExportConstants::LARGE_DATASET_WARNING_THRESHOLD, ExportConstants::LONG_PROCESS_DAYS_THRESHOLD, ExportConstants::LONG_PROCESS_QUEUE, ExportConstants::MAX_RETRIES, ExportConstants::PDF_FILE_EXTENSION, ExportConstants::PROGRESS_MESSAGES, ExportConstants::PROGRESS_PHASES, ExportConstants::PROGRESS_UPDATE_BATCH_SIZE, ExportConstants::RESERVATION_BATCH_SIZE, ExportConstants::RETRY_BASE_INTERVAL, ExportConstants::RETRY_MAX_ELAPSED_TIME, ExportConstants::ZERO_COMMISSION_CHANNEL_ID
Instance Attribute Summary collapse
-
#blogger_channel_id ⇒ Object
Returns the value of attribute blogger_channel_id.
-
#corporate_event_id ⇒ Object
Returns the value of attribute corporate_event_id.
-
#country_ids ⇒ Object
Returns the value of attribute country_ids.
-
#data_type ⇒ Object
Returns the value of attribute data_type.
-
#date_filter_type ⇒ Object
Returns the value of attribute date_filter_type.
-
#date_type ⇒ Object
Reservation list is collected based on date creation or dining date valid value is :date, or :created_at.
-
#emails ⇒ Object
if receiver is hungryhub staff, then this attribute is required.
-
#end_date ⇒ Object
Returns the value of attribute end_date.
-
#end_time ⇒ Object
Returns the value of attribute end_time.
-
#pdf_url ⇒ Object
Returns the value of attribute pdf_url.
-
#receiver ⇒ Object
Returns the value of attribute receiver.
-
#restaurant_group_id ⇒ Object
Returns the value of attribute restaurant_group_id.
-
#restaurant_id ⇒ Object
Returns the value of attribute restaurant_id.
-
#restaurant_partner_id ⇒ Object
Returns the value of attribute restaurant_partner_id.
-
#staff_id ⇒ Object
Returns the value of attribute staff_id.
-
#start_date ⇒ Object
Returns the value of attribute start_date.
-
#start_time ⇒ Object
Returns the value of attribute start_time.
-
#to_hh_team_only ⇒ Object
Returns the value of attribute to_hh_team_only.
Class Method Summary collapse
Instance Method Summary collapse
- #collect_reservations ⇒ Object
- #corporates_event ⇒ Object
- #delete_keda_identifier ⇒ Object
- #filter_by_country_ids(reservations) ⇒ Object
- #filter_by_range_date_with_date_type(reservations, start_date, end_date) ⇒ Object
- #filter_by_restaurant_group(reservations) ⇒ Object
- #filter_by_single_date(reservations, start_date, start_time, end_time) ⇒ Object
- #filter_by_single_date_with_date_type(reservations, start_date) ⇒ Object
- #filter_by_staff_restaurants(reservations) ⇒ Object
-
#perform(receiver_type, args, attachment_id = nil) ⇒ Object
Example payload when downloading report from Admin Dashboard, for Thailand country, Dining Type = DATE, and for HH Staff NotificationWorkers::ReservationReport.perform_async(hh_staff“, href=""user@domain.com"">emails“=>, ”restaurant_id“=>”“, . ”restaurant_group_id“=>”“, ”start_date“=>”2025-06-26“, ”end_date“=>”2025-06-30“, ”date_type“=>”date“, ”start_time“=>”“, ”end_time“=>”“, ”date_filter_type“=>”range“, ”country_ids“=>, ”use_long_process“=>true, . attachment_id).
- #total_gift_card_amount(active_reservation_vouchers) ⇒ Object
- #total_promo_code_amount(active_reservation_vouchers) ⇒ Object
- #total_redemption_amount(vouchers) ⇒ Object
- #total_staled_keda_identifier ⇒ Object
Methods included from ReservationExportHelpers
#calc_prepayment, #calculate_commission, #calculate_restaurant_revenue, #calculate_total_package_price, #date_format, #decimal_format, #format_add_on_names_with_quantity, #format_addon_names_for_report, #format_money, #format_package_names, #format_package_names_for_report, #format_package_names_with_quantity, #format_voucher_amount, #format_voucher_code, #get_reservation_distance, #paid_amount, #restaurant_delivery_fee, #sanitize_package_name, #sanitizer, #valid_email?, #voucher_amount, #voucher_code
Methods inherited from ApplicationWorker
Instance Attribute Details
#blogger_channel_id ⇒ Object
Returns the value of attribute blogger_channel_id.
38 39 40 |
# File 'app/workers/notification_workers/reservation_report.rb', line 38 def blogger_channel_id @blogger_channel_id end |
#corporate_event_id ⇒ Object
Returns the value of attribute corporate_event_id.
38 39 40 |
# File 'app/workers/notification_workers/reservation_report.rb', line 38 def corporate_event_id @corporate_event_id end |
#country_ids ⇒ Object
Returns the value of attribute country_ids.
38 39 40 |
# File 'app/workers/notification_workers/reservation_report.rb', line 38 def country_ids @country_ids end |
#data_type ⇒ Object
Returns the value of attribute data_type.
38 39 40 |
# File 'app/workers/notification_workers/reservation_report.rb', line 38 def data_type @data_type end |
#date_filter_type ⇒ Object
Returns the value of attribute date_filter_type.
38 39 40 |
# File 'app/workers/notification_workers/reservation_report.rb', line 38 def date_filter_type @date_filter_type end |
#date_type ⇒ Object
Reservation list is collected based on date creation or dining date valid value is :date, or :created_at
52 53 54 |
# File 'app/workers/notification_workers/reservation_report.rb', line 52 def date_type @date_type end |
#emails ⇒ Object
if receiver is hungryhub staff, then this attribute is required
48 49 50 |
# File 'app/workers/notification_workers/reservation_report.rb', line 48 def emails @emails end |
#end_date ⇒ Object
Returns the value of attribute end_date.
38 39 40 |
# File 'app/workers/notification_workers/reservation_report.rb', line 38 def end_date @end_date end |
#end_time ⇒ Object
Returns the value of attribute end_time.
38 39 40 |
# File 'app/workers/notification_workers/reservation_report.rb', line 38 def end_time @end_time end |
#pdf_url ⇒ Object
Returns the value of attribute pdf_url.
38 39 40 |
# File 'app/workers/notification_workers/reservation_report.rb', line 38 def pdf_url @pdf_url end |
#receiver ⇒ Object
Returns the value of attribute receiver.
38 39 40 |
# File 'app/workers/notification_workers/reservation_report.rb', line 38 def receiver @receiver end |
#restaurant_group_id ⇒ Object
Returns the value of attribute restaurant_group_id.
38 39 40 |
# File 'app/workers/notification_workers/reservation_report.rb', line 38 def restaurant_group_id @restaurant_group_id end |
#restaurant_id ⇒ Object
Returns the value of attribute restaurant_id.
38 39 40 |
# File 'app/workers/notification_workers/reservation_report.rb', line 38 def restaurant_id @restaurant_id end |
#restaurant_partner_id ⇒ Object
Returns the value of attribute restaurant_partner_id.
38 39 40 |
# File 'app/workers/notification_workers/reservation_report.rb', line 38 def restaurant_partner_id @restaurant_partner_id end |
#staff_id ⇒ Object
Returns the value of attribute staff_id.
38 39 40 |
# File 'app/workers/notification_workers/reservation_report.rb', line 38 def staff_id @staff_id end |
#start_date ⇒ Object
Returns the value of attribute start_date.
38 39 40 |
# File 'app/workers/notification_workers/reservation_report.rb', line 38 def start_date @start_date end |
#start_time ⇒ Object
Returns the value of attribute start_time.
38 39 40 |
# File 'app/workers/notification_workers/reservation_report.rb', line 38 def start_time @start_time end |
#to_hh_team_only ⇒ Object
Returns the value of attribute to_hh_team_only.
38 39 40 |
# File 'app/workers/notification_workers/reservation_report.rb', line 38 def to_hh_team_only @to_hh_team_only end |
Class Method Details
.fix_queue_perform_async(receiver, args) ⇒ Object
731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 |
# File 'app/workers/notification_workers/reservation_report.rb', line 731 def self.fix_queue_perform_async(receiver, args) start_date = args[:start_date] end_date = args[:end_date] report_type = args.delete(:report_type) || :booking corporate_event_id = args[:corporate_event_id] use_long_process = if start_date.present? && end_date.present? (end_date - start_date).to_i >= 3 elsif corporate_event_id.present? Corporates::Event.find(corporate_event_id).reservations.count > 100 else raise NotImplementedError end = if [:owner_staff, :restaurant_managers, :hh_staff].include?(receiver) Attachment.create( restaurant_id: args[:restaurant_id], exported_by: receiver, status: :on_queue, report_type: report_type, name: NotificationWorkers::ReservationReport.generate_name(report_type, args), ) end job_id = if use_long_process client = Sidekiq::Client.new client.push('class' => 'NotificationWorkers::ReservationReport', 'queue' => 'longprocess', 'args' => [receiver, args.merge(use_long_process: true), &.id]) else NotificationWorkers::ReservationReport.perform_async(receiver, args, &.id) end if .present? && .persisted? .job_id = job_id .save! end job_id end |
.generate_name(report_type, args) ⇒ Object
771 772 773 774 775 776 777 |
# File 'app/workers/notification_workers/reservation_report.rb', line 771 def self.generate_name(report_type, args) if args[:staff_id].present? "Report_#{report_type}_all_branch_#{args[:data_type]}_#{SecureRandom.hex(3)}" else "Report_#{report_type}_#{args[:data_type]}_#{SecureRandom.hex(3)}" end end |
Instance Method Details
#collect_reservations ⇒ Object
613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 |
# File 'app/workers/notification_workers/reservation_report.rb', line 613 def collect_reservations log_memory_usage('Before collecting reservations') base_query = if corporate_event_id.present? corporates_event.reservations.reached_goal_scope.includes(:customer, :user, :voucher, :address, :paid_charges, gb_merchant: :restaurants, restaurant: :translations) else reservations = Reservation.includes(:customer, :user, :voucher, :address, :paid_charges, :vendor_reservation, :restaurant, gb_merchant: :restaurants, restaurant: :translations). where(restaurant_translations: { locale: :en }).exclude_temporary reservations = if date_filter_type == 'single' if for_affiliate_dashboard? filter_by_single_date_with_date_type(reservations, start_date) else filter_by_single_date(reservations, start_date, start_time, end_time) end else filter_by_range_date_with_date_type(reservations, start_date, end_date) end reservations = reservations.hungryhub unless for_owner_staff? reservations = reservations.where(restaurant_id: restaurant_id) if report_single_restaurant? reservations = filter_by_country_ids(reservations) reservations = filter_by_restaurant_group(reservations) if report_group_restaurant? reservations = filter_by_staff_restaurants(reservations) if report_group_by_staff? if for_invoice_to_restaurant? reservations = reservations.where(active: true, ack: true). joins(:property). where(reservation_properties: { covered_by_hh: false }). where.not(channel: CHANNEL_IDS_TO_SKIP) # Apply status filtering for restaurant managers report reservations = filter_by_restaurant_manager_status(reservations) end if for_restaurant_partner? reservations = reservations.joins(:reservation_packages).where(reservation_packages: { restaurant_package_id: restaurant_partner.staff_packages.map(&:restaurant_package_id), }) end reservations = reservations.where(channel: blogger_channel_id) if for_affiliate_dashboard? reservations end # Log the query details for monitoring total_count = base_query.count BUSINESS_LOGGER.set_business_context({ worker_class: self.class.name, receiver: receiver, restaurant_id: restaurant_id, }) BUSINESS_LOGGER.info('Collected reservations query', { total_count: total_count, date_range: "#{start_date} to #{end_date}", includes_eager_loading: true, }) log_memory_usage('After building reservations query') # Return the query object, not the loaded results base_query rescue StandardError => e APMErrorHandler.report e raise e end |
#corporates_event ⇒ Object
609 610 611 |
# File 'app/workers/notification_workers/reservation_report.rb', line 609 def corporates_event @corporates_event ||= Corporates::Event.find corporate_event_id end |
#delete_keda_identifier ⇒ Object
779 780 781 782 783 |
# File 'app/workers/notification_workers/reservation_report.rb', line 779 def delete_keda_identifier $persistent_redis.with do |redis| redis.lpop(LONG_PROCESS_MASTER) end end |
#filter_by_country_ids(reservations) ⇒ Object
680 681 682 683 684 685 |
# File 'app/workers/notification_workers/reservation_report.rb', line 680 def filter_by_country_ids(reservations) # `all` radio button causing `on` value return reservations if country_ids.blank? || country_ids&.first == 'on' reservations.where(restaurants: { country_id: country_ids }) end |
#filter_by_range_date_with_date_type(reservations, start_date, end_date) ⇒ Object
723 724 725 726 727 728 729 |
# File 'app/workers/notification_workers/reservation_report.rb', line 723 def filter_by_range_date_with_date_type(reservations, start_date, end_date) if date_type == 'created_at' reservations.where('reservations.created_at BETWEEN ? AND ?', start_date.beginning_of_day, end_date.end_of_day) else reservations.where('reservations.date >= ? AND reservations.date <= ?', start_date, end_date) end end |
#filter_by_restaurant_group(reservations) ⇒ Object
687 688 689 690 691 692 693 |
# File 'app/workers/notification_workers/reservation_report.rb', line 687 def filter_by_restaurant_group(reservations) restaurant_ids = RestaurantGroup.find(restaurant_group_id).restaurants.pluck(:id) reservations.where(restaurant_id: restaurant_ids) rescue StandardError => e APMErrorHandler.report e raise e end |
#filter_by_single_date(reservations, start_date, start_time, end_time) ⇒ Object
703 704 705 706 707 708 709 710 711 712 713 |
# File 'app/workers/notification_workers/reservation_report.rb', line 703 def filter_by_single_date(reservations, start_date, start_time, end_time) reservations = reservations.where(date: start_date) if start_time == end_time reservations.where(start_time: start_time) else reservations.where('start_time >= ? AND start_time <= ?', start_time, end_time) end rescue StandardError => e APMErrorHandler.report e raise e end |
#filter_by_single_date_with_date_type(reservations, start_date) ⇒ Object
715 716 717 718 719 720 721 |
# File 'app/workers/notification_workers/reservation_report.rb', line 715 def filter_by_single_date_with_date_type(reservations, start_date) if date_type == 'created_at' reservations.where('reservations.created_at BETWEEN ? AND ?', start_date.beginning_of_day, start_date.end_of_day) else reservations.where(reservations: { date: start_date }) end end |
#filter_by_staff_restaurants(reservations) ⇒ Object
695 696 697 698 699 700 701 |
# File 'app/workers/notification_workers/reservation_report.rb', line 695 def filter_by_staff_restaurants(reservations) restaurant_ids = Staff.find_by(id: staff_id).restaurants.pluck(:id) reservations.where(restaurant_id: restaurant_ids) rescue StandardError => e APMErrorHandler.report e raise e end |
#perform(receiver_type, args, attachment_id = nil) ⇒ Object
Example payload when downloading report from Admin Dashboard, for Thailand country, Dining Type = DATE, and for HH Staff
NotificationWorkers::ReservationReport.perform_async(hh_staff", {"emails"=>["user@domain.com"], "restaurant_id"=>"",
. “restaurant_group_id”=>“”, “start_date”=>“2025-06-26”, “end_date”=>“2025-06-30”, “date_type”=>“date”,
"start_time"=>"", "end_time"=>"", "date_filter_type"=>"range", "country_ids"=>["218"], "use_long_process"=>true},
. attachment_id)
280 281 282 283 284 285 286 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 518 519 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 |
# File 'app/workers/notification_workers/reservation_report.rb', line 280 def perform(receiver_type, args, = nil) @start_time = Time.current @last_gc_stats = GC.stat if defined?(GC) && GC.respond_to?(:stat) @last_memory_log_time = nil log_memory_usage('Worker started') use_keda = args.with_indifferent_access.fetch(:use_long_process, false).to_s == 'true' # Retry options configuration = { on: [Aws::SES::Errors::Throttling], tries: 2, base_interval: 1, max_elapsed_time: 60, on_retry: Proc.new do |_, try, elapsed_time, _| APMErrorHandler.report("Retrying after #{elapsed_time} seconds... (Attempt #{try})") store status: Attachment::RETRYING_STATUS store message: "Retrying after #{elapsed_time} seconds... (Attempt #{try})" end, } total 100 store status: Attachment::ON_PROGRESS_STATUS store email_status: Attachment::PENDING_EMAIL_STATUS at 0 = Attachment.find_by(id: ) .update(status: :on_progress) if .present? Retriable.retriable() do create_keda_identifier if use_keda self.receiver = receiver_type.to_sym args.to_h.with_indifferent_access.each do |k, v| next if k.to_s == 'use_long_process' send("#{k}=".to_sym, v) end # Set default data_type to 'excel' if not provided self.data_type = 'excel' if data_type.blank? I18n.locale = :en # Force cleanup before loading large dataset force_memory_cleanup! self.reservations = collect_reservations # Cache the count to avoid repeated database calls @total_reservations_count = reservations.count BUSINESS_LOGGER.set_business_context({ worker_class: self.class.name, receiver: receiver, total_reservations: @total_reservations_count, }) BUSINESS_LOGGER.info('Processing reservations', { total_count: @total_reservations_count, receiver_type: receiver, }) if for_hh? if corporate_event_id.present? generate_corporate_event_excel_report_for_hh else generate_excel_report_for_hh end # Clean up after Excel generation force_memory_cleanup! self. = () emails_sent = false @emails.in_groups_of(40, false) do |email_receivers| valid_receivers = valid_email_receivers(email_receivers) if valid_receivers.present? StaffMailer.reservation_report(valid_receivers, download_link, [], email_subject).deliver_later! emails_sent = true end end unless emails_sent APMErrorHandler.report('No valid emails found for sending HH staff report', { emails: @emails, receiver: receiver, start_date: start_date, end_date: end_date, }) end elsif for_invoice_to_restaurant? && @total_reservations_count == 0 all_emails = (Array(restaurant.user&.email).compact + Array(receivers).compact).uniq validated_emails = valid_email_receivers(all_emails) if validated_emails.present? # Send all emails as 'to' recipients to ensure delivery even if one email is invalid validated_emails.in_groups_of(40, false) do |email_group| OwnerMailer.has_no_reservation_report( restaurant.id, email_subject, start_date.to_s, email_group, [], ).deliver_later! end else APMErrorHandler.report('No valid emails found for sending report', { restaurant_id: restaurant_id, all_emails: all_emails, }) end elsif for_invoice_to_restaurant? && @total_reservations_count.positive? if pdf? report = ReportPdf.new args = { restaurant_id: restaurant_id, start_date: start_date, end_date: end_date, date_filter_type: 'range', } report.generate_pdf(reservations, args) self.pdf_url = report.pdf_url at(90) self. = (, 'pdf') else generate_invoice_report_to_restaurant self. = () end all_emails = (Array(restaurant.user&.email).compact + Array(receivers).compact).uniq validated_emails = valid_email_receivers(all_emails) if validated_emails.present? # Send all emails as 'to' recipients to ensure delivery even if one email is invalid validated_emails.in_groups_of(40, false) do |email_group| OwnerMailer.reservation_report( restaurant.id, email_body, email_subject, email_group, [], ).deliver_later! end else APMErrorHandler.report('No valid emails found for sending report', { restaurant_id: restaurant_id, all_emails: all_emails, }) end elsif for_owner_staff? && @total_reservations_count.positive? if excel? generate_excel_report_for_owner_staff self. = () elsif pdf? report = ReportPdf.new args = { restaurant_id: restaurant_id, restaurant_group_id: restaurant_group_id, start_date: start_date, end_date: end_date, date_filter_type: date_filter_type, staff_id: staff_id, } report.generate_pdf(reservations, args) self.pdf_url = report.pdf_url at(90) self. = (, 'pdf') end emails_sent = false receivers.in_groups_of(40, false) do |email_receivers| valid_receivers = valid_email_receivers(email_receivers) if valid_receivers.present? StaffMailer.reservation_report_staff(email_body, email_subject, valid_receivers).deliver_later! emails_sent = true end end unless emails_sent APMErrorHandler.report('No valid emails found for sending owner staff report', { emails: @emails, receiver: receiver, restaurant_id: restaurant_id, restaurant_group_id: restaurant_group_id, }) end elsif for_restaurant_partner? generate_excel_report_for_restaurant_partner self. = () emails_sent = false @emails.in_groups_of(40, false) do |email_receivers| valid_receivers = valid_email_receivers(email_receivers) if valid_receivers.present? StaffMailer.reservation_report(receivers, download_link, valid_receivers, email_subject).deliver_later! emails_sent = true end end unless emails_sent APMErrorHandler.report('No valid emails found for sending restaurant partner report', { emails: @emails, receiver: receiver, restaurant_partner_id: restaurant_partner_id, }) end elsif for_affiliate_dashboard? && @total_reservations_count == 0 emails_sent = false receivers.in_groups_of(40, false) do |email_receivers| valid_receivers = valid_email_receivers(email_receivers) if valid_receivers.present? AffiliatesDashboardMailer.has_no_reservation_affiliate_dashboard(email_body, email_subject, valid_receivers).deliver_later! emails_sent = true end end unless emails_sent APMErrorHandler.report('No valid emails found for sending affiliate dashboard report (no reservations)', { emails: @emails, receiver: receiver, blogger_channel_id: blogger_channel_id, }) end elsif for_affiliate_dashboard? && @total_reservations_count.positive? if excel? generate_excel_report_for_affiliate_dashboard self. = () elsif pdf? report = ReportPdf.new args = { start_date: start_date, end_date: end_date, date_filter_type: date_filter_type, } report.generate_pdf(reservations, args) self.pdf_url = report.pdf_url end emails_sent = false receivers.in_groups_of(40, false) do |email_receivers| valid_receivers = valid_email_receivers(email_receivers) if valid_receivers.present? AffiliatesDashboardMailer.reservation_affiliate_dashboard(email_body, email_subject, valid_receivers).deliver_later! emails_sent = true end end unless emails_sent APMErrorHandler.report('No valid emails found for sending affiliate dashboard report', { emails: @emails, receiver: receiver, blogger_channel_id: blogger_channel_id, }) end elsif @total_reservations_count.zero? store status: Attachment::FAILED_STATUS return false else store status: Attachment::FAILED_STATUS APMErrorHandler.report('unknown booking report receiver') raise NotImplementedError end store email_status: Attachment::SENT_EMAIL_STATUS delete_report() if excel? rescue Aws::SES::Errors::Throttling => e store email_status: Attachment::FAILED_EMAIL_STATUS store message: "Email sending failed due to AWS SES throttling. Error: #{e.}" # Retriable gem will handle retries for this specific error. raise e rescue NoMemoryError => e # Handle memory exhaustion specifically store email_status: Attachment::FAILED_EMAIL_STATUS store status: Attachment::FAILED_STATUS store message: "Job failed due to memory exhaustion. Try a smaller date range." APMErrorHandler.report("Memory exhaustion in ReservationReport", { error: e., receiver: receiver, start_date: start_date, end_date: end_date, total_reservations: @total_reservations_count, }) # Force immediate cleanup GC.start raise e rescue StandardError => e store email_status: Attachment::FAILED_EMAIL_STATUS store message: "An error occurred: #{e.}" store status: Attachment::FAILED_STATUS if .blank? || .download_link.blank? # Handle other standard errors here if needed APMErrorHandler.report("An error occurred: #{e.}") raise e ensure # Update attachment status to failed if job is interrupted (e.g., OOM kill) # This helps identify jobs that were killed without proper cleanup begin if .present? # Reload to get fresh status from database .reload if .status == 'on_progress' .update( status: :failed, message: 'Job was interrupted unexpectedly (possible OOM kill or timeout)', ) BUSINESS_LOGGER.warn('ReservationReport job interrupted', { attachment_id: , receiver: receiver, total_reservations: @total_reservations_count, }) end end rescue StandardError => cleanup_error # Don't raise errors in ensure block APMErrorHandler.report("Error in cleanup: #{cleanup_error.}") end # Clean up worker instance variables and force final garbage collection cleanup_worker_variables! delete_keda_identifier if use_keda log_memory_usage('Worker completed') end end |
#total_gift_card_amount(active_reservation_vouchers) ⇒ Object
796 797 798 799 800 801 802 803 804 |
# File 'app/workers/notification_workers/reservation_report.rb', line 796 def total_gift_card_amount(active_reservation_vouchers) return '' if active_reservation_vouchers.blank? gift_vouchers = active_reservation_vouchers.joins(:voucher).where(vouchers: { voucher_category: 'gift' }) return humanized_money(gift_vouchers.sum(&:amount)) if gift_vouchers.present? # Return empty string if there is no gift voucher '' end |
#total_promo_code_amount(active_reservation_vouchers) ⇒ Object
806 807 808 809 810 811 812 |
# File 'app/workers/notification_workers/reservation_report.rb', line 806 def total_promo_code_amount(active_reservation_vouchers) return '' if active_reservation_vouchers.blank? promo_code_vouchers = active_reservation_vouchers.joins(:voucher).where.not(vouchers: { voucher_category: ['gift', 'redemption'] }) humanized_money(promo_code_vouchers.sum(&:amount)) if promo_code_vouchers.present? end |
#total_redemption_amount(vouchers) ⇒ Object
814 815 816 817 818 |
# File 'app/workers/notification_workers/reservation_report.rb', line 814 def total_redemption_amount(vouchers) return '' if vouchers.redeemed_points.blank? vouchers.redeemed_points.sum(&:amount).to_i end |
#total_staled_keda_identifier ⇒ Object
785 786 787 788 789 790 791 792 793 794 |
# File 'app/workers/notification_workers/reservation_report.rb', line 785 def total_staled_keda_identifier queue = Sidekiq::Queue.new('longprocess') queue_size = queue.size keda_identifier = nil $persistent_redis.with do |redis| keda_identifier = redis.llen(LONG_PROCESS_MASTER) end keda_identifier.to_i - queue_size.to_i end |