Kulcare Docs
DeveloperAuthentication Documentation04 featuresRevenue sharing commissions

Revenue Sharing & Commission System Implementation Plan

System Architecture Overview

1. Database Schema Design

1.1 Revenue Sharing Tables

Database Schema Diagram

revenue_share_configurations

Primary table for multi-location revenue sharing setups.

Fields:

Fields:

  • id (bigint, primary key)
  • clinic_id (bigint, foreign key to clinics table)
  • configuration_name (varchar(255), user-defined name)
  • description (text, optional)
  • distribution_rules (jsonb, distribution logic and rules)
  • approval_required (boolean, default: true)
  • effective_from (timestamp)
  • effective_until (timestamp, nullable)
  • status (enum: 'draft', 'active', 'inactive')
  • created_by_user_id (bigint, foreign key to users)
  • created_at (timestamp)
  • updated_at (timestamp)

Indexes:

  • clinic_id, status, effective_from
  • created_by_user_id

revenue_share_stakeholders

Stakeholders for revenue sharing (linked to users with specific roles).

Fields:

  • id (bigint, primary key)
  • configuration_id (bigint, foreign key to revenue_share_configurations)
  • user_id (bigint, foreign key to users - must have clinic_owner/admin/staff/consultant role)
  • stakeholder_type (enum: 'clinic_owner', 'partner', 'investor', 'referrer', 'consultant')
  • share_percentage (decimal(5,2), nullable)
  • share_amount (decimal(12,2), nullable)
  • minimum_amount (decimal(12,2), default: 0)
  • maximum_amount (decimal(12,2), nullable)
  • conditions (jsonb, conditional distribution rules)
  • billing_account_id (bigint, foreign key to billing_accounts, nullable)
  • payout_method (enum: 'bank_transfer', 'razorpay', 'manual')
  • active (boolean, default: true)
  • created_at (timestamp)
  • updated_at (timestamp)

Indexes:

  • configuration_id, user_id, active
  • user_id, stakeholder_type

revenue_shares

Actual revenue distribution records linked to payments.

Fields:

  • id (bigint, primary key)
  • billing_payment_id (bigint, foreign key to billing_payments)
  • stakeholder_id (bigint, foreign key to revenue_share_stakeholders)
  • amount (decimal(12,2), actual distributed amount)
  • percentage (decimal(5,2), percentage used for calculation)
  • calculation_data (jsonb, detailed calculation breakdown)
  • status (enum: 'pending', 'approved', 'distributed', 'failed', 'cancelled')
  • distributed_at (timestamp, nullable)
  • created_at (timestamp)
  • updated_at (timestamp)

Indexes:

  • billing_payment_id, status
  • stakeholder_id, status, distributed_at

1.2 Commission Management Tables

commission_rules

Commission rules for healthcare professionals.

Fields:

  • id (bigint, primary key)
  • clinic_id (bigint, foreign key to clinics)
  • rule_name (varchar(255), descriptive name)
  • rule_type (enum: 'percentage', 'fixed_amount', 'tiered', 'performance_bonus')
  • target_entity (enum: 'consultation', 'service', 'revenue', 'patient_satisfaction')
  • conditions (jsonb, eligibility conditions)
  • calculation_method (jsonb, calculation parameters)
  • effective_from (timestamp)
  • effective_until (timestamp, nullable)
  • active (boolean, default: true)
  • created_by_user_id (bigint, foreign key to users)
  • created_at (timestamp)
  • updated_at (timestamp)

Indexes:

  • clinic_id, active, effective_from
  • rule_type, target_entity

commission_calculations

Calculated commission amounts for healthcare professionals.

Fields:

  • id (bigint, primary key)
  • commission_rule_id (bigint, foreign key to commission_rules)
  • clinic_doctor_id (bigint, foreign key to clinic_doctors)
  • billing_payment_id (bigint, foreign key to billing_payments, nullable)
  • base_amount (decimal(12,2), amount used for calculation)
  • commission_amount (decimal(12,2), calculated commission)
  • calculation_details (jsonb, detailed breakdown)
  • calculation_period (varchar(50), 'daily', 'weekly', 'monthly')
  • status (enum: 'calculated', 'approved', 'paid', 'cancelled')
  • approved_by_user_id (bigint, foreign key to users, nullable)
  • calculated_at (timestamp)
  • created_at (timestamp)
  • updated_at (timestamp)

Indexes:

  • clinic_doctor_id, status, calculated_at
  • commission_rule_id, calculation_period
  • billing_payment_id

commission_payments

Actual commission payment records.

Fields:

  • id (bigint, primary key)
  • commission_calculation_id (bigint, foreign key to commission_calculations)
  • billing_account_id (bigint, foreign key to billing_accounts)
  • amount (decimal(12,2))
  • payment_method (enum: 'bank_transfer', 'razorpay', 'manual')
  • payment_reference (varchar(255), external payment reference)
  • payment_status (enum: 'pending', 'processing', 'completed', 'failed')
  • processed_at (timestamp, nullable)
  • created_at (timestamp)
  • updated_at (timestamp)

Indexes:

  • commission_calculation_id
  • billing_account_id, payment_status
  • processed_at

1.3 Workflow & Audit Tables

approval_workflows

Approval workflow tracking for revenue sharing and commissions.

Fields:

  • id (bigint, primary key)
  • entity_type (enum: 'revenue_share', 'commission_calculation', 'commission_rule')
  • entity_id (bigint, polymorphic reference)
  • workflow_config (jsonb, workflow configuration)
  • current_step (integer, current approval step)
  • status (enum: 'pending', 'approved', 'rejected', 'escalated')
  • approved_by_user_id (bigint, foreign key to users, nullable)
  • approved_at (timestamp, nullable)
  • rejection_reason (text, nullable)
  • created_at (timestamp)
  • updated_at (timestamp)

Indexes:

  • entity_type, entity_id, status
  • approved_by_user_id

audit_trails

Comprehensive audit trail for all financial operations.

Fields:

  • id (bigint, primary key)
  • entity_type (varchar(100), table name)
  • entity_id (bigint, record id)
  • action (enum: 'create', 'update', 'delete', 'approve', 'calculate', 'distribute')
  • user_id (bigint, foreign key to users)
  • changes (jsonb, before/after values)
  • metadata (jsonb, additional context)
  • ip_address (varchar(45))
  • user_agent (text)
  • created_at (timestamp)

Indexes:

  • entity_type, entity_id, created_at
  • user_id, created_at
  • action, created_at

1.4 Reporting & Analytics Tables

custom_reports

User-defined custom reports.

Fields:

  • id (bigint, primary key)
  • clinic_id (bigint, foreign key to clinics)
  • report_name (varchar(255))
  • report_type (enum: 'revenue_sharing', 'commission', 'combined', 'custom')
  • report_config (jsonb, report parameters and filters)
  • created_by_user_id (bigint, foreign key to users)
  • shared_with_users (jsonb, array of user IDs)
  • active (boolean, default: true)
  • created_at (timestamp)
  • updated_at (timestamp)

Indexes:

  • clinic_id, active
  • created_by_user_id

scheduled_reports

Automated report generation and delivery.

Fields:

  • id (bigint, primary key)
  • custom_report_id (bigint, foreign key to custom_reports)
  • schedule_config (jsonb, scheduling parameters)
  • recipient_config (jsonb, delivery recipients and methods)
  • last_run_at (timestamp, nullable)
  • next_run_at (timestamp, nullable)
  • active (boolean, default: true)
  • created_at (timestamp)
  • updated_at (timestamp)

Indexes:

  • next_run_at, active
  • custom_report_id

user_training_progress

User training completion tracking for system features.

Fields:

  • id (bigint, primary key)
  • user_id (bigint, foreign key to users)
  • training_module (enum: 'revenue_sharing_basic', 'commission_setup', 'report_creation', 'approval_workflow')
  • completion_status (enum: 'not_started', 'in_progress', 'completed')
  • completed_at (timestamp, nullable)
  • quiz_score (integer, nullable)
  • created_at (timestamp)
  • updated_at (timestamp)

Indexes:

  • user_id, training_module
  • completion_status

2. Migration Scripts

2.1 Revenue Sharing Tables Migration

class CreateRevenueShareConfiguration < ActiveRecord::Migration[7.1]
  def change
    create_table :revenue_share_configurations do |t|
      t.references :clinic, null: false, foreign_key: true
      t.string :configuration_name, null: false, limit: 255
      t.text :description
      t.jsonb :distribution_rules, null: false, default: {}
      t.boolean :approval_required, default: true
      t.timestamp :effective_from, null: false
      t.timestamp :effective_until
      t.string :status, default: 'draft', null: false
      t.references :created_by_user, null: false, foreign_key: { to_table: :users }
      t.timestamps

      t.index [:clinic_id, :status, :effective_from]
      t.index :created_by_user_id
    end

    create_table :revenue_share_stakeholders do |t|
      t.references :configuration, null: false, foreign_key: { to_table: :revenue_share_configurations }
      t.references :user, null: false, foreign_key: true
      t.string :stakeholder_type, null: false
      t.decimal :share_percentage, precision: 5, scale: 2
      t.decimal :share_amount, precision: 12, scale: 2
      t.decimal :minimum_amount, precision: 12, scale: 2, default: 0
      t.decimal :maximum_amount, precision: 12, scale: 2
      t.jsonb :conditions, default: {}
      t.references :billing_account, foreign_key: true
      t.string :payout_method, null: false, default: 'manual'
      t.boolean :active, default: true
      t.timestamps

      t.index [:configuration_id, :user_id, :active]
      t.index [:user_id, :stakeholder_type]
    end

    create_table :revenue_shares do |t|
      t.references :billing_payment, null: false, foreign_key: true
      t.references :stakeholder, null: false, foreign_key: { to_table: :revenue_share_stakeholders }
      t.decimal :amount, precision: 12, scale: 2, null: false
      t.decimal :percentage, precision: 5, scale: 2, null: false
      t.jsonb :calculation_data, default: {}
      t.string :status, default: 'pending', null: false
      t.timestamp :distributed_at
      t.timestamps

      t.index [:billing_payment_id, :status]
      t.index [:stakeholder_id, :status, :distributed_at]
    end
  end
end

2.2 Commission Tables Migration

class CreateCommissionTables < ActiveRecord::Migration[7.1]
  def change
    create_table :commission_rules do |t|
      t.references :clinic, null: false, foreign_key: true
      t.string :rule_name, null: false, limit: 255
      t.string :rule_type, null: false
      t.string :target_entity, null: false
      t.jsonb :conditions, default: {}
      t.jsonb :calculation_method, null: false, default: {}
      t.timestamp :effective_from, null: false
      t.timestamp :effective_until
      t.boolean :active, default: true
      t.references :created_by_user, null: false, foreign_key: { to_table: :users }
      t.timestamps

      t.index [:clinic_id, :active, :effective_from]
      t.index [:rule_type, :target_entity]
    end

    create_table :commission_calculations do |t|
      t.references :commission_rule, null: false, foreign_key: true
      t.references :clinic_doctor, null: false, foreign_key: true
      t.references :billing_payment, foreign_key: true
      t.decimal :base_amount, precision: 12, scale: 2, null: false
      t.decimal :commission_amount, precision: 12, scale: 2, null: false
      t.jsonb :calculation_details, default: {}
      t.string :calculation_period, limit: 50
      t.string :status, default: 'calculated', null: false
      t.references :approved_by_user, foreign_key: { to_table: :users }
      t.timestamp :calculated_at, null: false
      t.timestamps

      t.index [:clinic_doctor_id, :status, :calculated_at]
      t.index [:commission_rule_id, :calculation_period]
      t.index :billing_payment_id
    end

    create_table :commission_payments do |t|
      t.references :commission_calculation, null: false, foreign_key: true
      t.references :billing_account, null: false, foreign_key: true
      t.decimal :amount, precision: 12, scale: 2, null: false
      t.string :payment_method, null: false
      t.string :payment_reference, limit: 255
      t.string :payment_status, default: 'pending', null: false
      t.timestamp :processed_at
      t.timestamps

      t.index :commission_calculation_id
      t.index [:billing_account_id, :payment_status]
      t.index :processed_at
    end
  end
end

2.3 Workflow & Audit Tables Migration

class CreateWorkflowAndAuditTables < ActiveRecord::Migration[7.1]
  def change
    create_table :approval_workflows do |t|
      t.string :entity_type, null: false
      t.bigint :entity_id, null: false
      t.jsonb :workflow_config, default: {}
      t.integer :current_step, default: 1
      t.string :status, default: 'pending', null: false
      t.references :approved_by_user, foreign_key: { to_table: :users }
      t.timestamp :approved_at
      t.text :rejection_reason
      t.timestamps

      t.index [:entity_type, :entity_id, :status]
      t.index :approved_by_user_id
    end

    create_table :audit_trails do |t|
      t.string :entity_type, null: false, limit: 100
      t.bigint :entity_id, null: false
      t.string :action, null: false
      t.references :user, null: false, foreign_key: true
      t.jsonb :changes, default: {}
      t.jsonb :metadata, default: {}
      t.string :ip_address, limit: 45
      t.text :user_agent
      t.timestamp :created_at, null: false

      t.index [:entity_type, :entity_id, :created_at]
      t.index [:user_id, :created_at]
      t.index [:action, :created_at]
    end
  end
end

2.4 Reporting Tables Migration

class CreateReportingTables < ActiveRecord::Migration[7.1]
  def change
    create_table :custom_reports do |t|
      t.references :clinic, null: false, foreign_key: true
      t.string :report_name, null: false, limit: 255
      t.string :report_type, null: false
      t.jsonb :report_config, null: false, default: {}
      t.references :created_by_user, null: false, foreign_key: { to_table: :users }
      t.jsonb :shared_with_users, default: []
      t.boolean :active, default: true
      t.timestamps

      t.index [:clinic_id, :active]
      t.index :created_by_user_id
    end

    create_table :scheduled_reports do |t|
      t.references :custom_report, null: false, foreign_key: true
      t.jsonb :schedule_config, null: false, default: {}
      t.jsonb :recipient_config, null: false, default: {}
      t.timestamp :last_run_at
      t.timestamp :next_run_at
      t.boolean :active, default: true
      t.timestamps

      t.index [:next_run_at, :active]
      t.index :custom_report_id
    end

    create_table :user_training_progress do |t|
      t.references :user, null: false, foreign_key: true
      t.string :training_module, null: false
      t.string :completion_status, default: 'not_started', null: false
      t.timestamp :completed_at
      t.integer :quiz_score
      t.timestamps

      t.index [:user_id, :training_module]
      t.index :completion_status
    end
  end
end

3. Business Logic Implementation

3.1 Service Classes Architecture

3.2 Core Models

RevenueShareConfiguration Model

# parts/billing/models/revenue_share_configuration.rb
class RevenueShareConfiguration < ApplicationRecord
  # Associations
  belongs_to :clinic
  belongs_to :created_by_user, class_name: 'User'
  has_many :revenue_share_stakeholders, dependent: :destroy
  has_many :stakeholder_users, through: :revenue_share_stakeholders, source: :user

  # Validations
  validates :configuration_name, presence: true, length: { maximum: 255 }
  validates :status, inclusion: { in: %w[draft active inactive] }
  validates :effective_from, presence: true
  validates :distribution_rules, presence: true
  validate :effective_dates_consistency
  validate :distribution_rules_valid
  validate :total_percentage_valid

  # Scopes
  scope :active, -> { where(status: 'active') }
  scope :for_clinic, ->(clinic_id) { where(clinic_id: clinic_id) }
  scope :effective_on, ->(date) { where('effective_from <= ? AND (effective_until IS NULL OR effective_until >= ?)', date, date) }

  # Enums
  enum status: { draft: 'draft', active: 'active', inactive: 'inactive' }

  private

  def effective_dates_consistency
    return unless effective_until.present? && effective_from > effective_until
    errors.add(:effective_until, 'must be after effective_from')
  end

  def distribution_rules_valid
    return if distribution_rules.is_a?(Hash) && distribution_rules.present?
    errors.add(:distribution_rules, 'must be a valid JSON object')
  end

  def total_percentage_valid
    total = revenue_share_stakeholders.sum(:share_percentage)
    return if total == 100.0 || total == 0.0
    errors.add(:base, 'Total percentage must equal 100%')
  end
end

RevenueShareStakeholder Model

# parts/billing/models/revenue_share_stakeholder.rb
class RevenueShareStakeholder < ApplicationRecord
  # Associations
  belongs_to :configuration, class_name: 'RevenueShareConfiguration'
  belongs_to :user
  belongs_to :billing_account, optional: true
  has_many :revenue_shares, foreign_key: :stakeholder_id, dependent: :destroy

  # Validations
  validates :stakeholder_type, inclusion: { in: %w[clinic_owner partner investor referrer consultant] }
  validates :payout_method, inclusion: { in: %w[bank_transfer razorpay manual] }
  validates :share_percentage, numericality: { greater_than: 0, less_than_or_equal_to: 100 }, allow_nil: true
  validates :share_amount, numericality: { greater_than: 0 }, allow_nil: true
  validate :percentage_or_amount_present
  validate :user_has_valid_role

  # Scopes
  scope :active, -> { where(active: true) }
  scope :by_type, ->(type) { where(stakeholder_type: type) }
  scope :for_user, ->(user_id) { where(user_id: user_id) }

  # Enums
  enum stakeholder_type: {
    clinic_owner: 'clinic_owner',
    partner: 'partner',
    investor: 'investor',
    referrer: 'referrer',
    consultant: 'consultant'
  }
  enum payout_method: {
    bank_transfer: 'bank_transfer',
    razorpay: 'razorpay',
    manual: 'manual'
  }

  private

  def percentage_or_amount_present
    return if share_percentage.present? || share_amount.present?
    errors.add(:base, 'Either share percentage or share amount must be specified')
  end

  def user_has_valid_role
    valid_roles = %w[clinic_owner admin staff consultant]
    user_roles = user.user_accounts.joins(:role).pluck('roles.name')
    return if (user_roles & valid_roles).any?
    errors.add(:user, 'must have clinic_owner, admin, staff, or consultant role')
  end
end

CommissionRule Model

# parts/billing/models/commission_rule.rb
class CommissionRule < ApplicationRecord
  # Associations
  belongs_to :clinic
  belongs_to :created_by_user, class_name: 'User'
  has_many :commission_calculations, dependent: :destroy

  # Validations
  validates :rule_name, presence: true, length: { maximum: 255 }
  validates :rule_type, inclusion: { in: %w[percentage fixed_amount tiered performance_bonus] }
  validates :target_entity, inclusion: { in: %w[consultation service revenue patient_satisfaction] }
  validates :effective_from, presence: true
  validates :calculation_method, presence: true
  validate :effective_dates_consistency
  validate :calculation_method_valid

  # Scopes
  scope :active, -> { where(active: true) }
  scope :for_clinic, ->(clinic_id) { where(clinic_id: clinic_id) }
  scope :effective_on, ->(date) { where('effective_from <= ? AND (effective_until IS NULL OR effective_until >= ?)', date, date) }
  scope :by_type, ->(type) { where(rule_type: type) }
  scope :by_entity, ->(entity) { where(target_entity: entity) }

  # Enums
  enum rule_type: {
    percentage: 'percentage',
    fixed_amount: 'fixed_amount',
    tiered: 'tiered',
    performance_bonus: 'performance_bonus'
  }
  enum target_entity: {
    consultation: 'consultation',
    service: 'service',
    revenue: 'revenue',
    patient_satisfaction: 'patient_satisfaction'
  }

  private

  def effective_dates_consistency
    return unless effective_until.present? && effective_from > effective_until
    errors.add(:effective_until, 'must be after effective_from')
  end

  def calculation_method_valid
    return if calculation_method.is_a?(Hash) && calculation_method.present?
    errors.add(:calculation_method, 'must be a valid JSON object')
  end
end

CommissionCalculation Model

# parts/billing/models/commission_calculation.rb
class CommissionCalculation < ApplicationRecord
  # Associations
  belongs_to :commission_rule
  belongs_to :clinic_doctor
  belongs_to :billing_payment, optional: true
  belongs_to :approved_by_user, class_name: 'User', optional: true
  has_many :commission_payments, dependent: :destroy

  # Validations
  validates :base_amount, :commission_amount, presence: true, numericality: { greater_than: 0 }
  validates :status, inclusion: { in: %w[calculated approved paid cancelled] }
  validates :calculation_period, inclusion: { in: %w[daily weekly monthly] }, allow_nil: true
  validates :calculated_at, presence: true

  # Scopes
  scope :pending_approval, -> { where(status: 'calculated') }
  scope :approved, -> { where(status: 'approved') }
  scope :paid, -> { where(status: 'paid') }
  scope :for_doctor, ->(doctor_id) { where(clinic_doctor_id: doctor_id) }
  scope :for_period, ->(period) { where(calculation_period: period) }
  scope :calculated_between, ->(start_date, end_date) { where(calculated_at: start_date..end_date) }

  # Enums
  enum status: {
    calculated: 'calculated',
    approved: 'approved',
    paid: 'paid',
    cancelled: 'cancelled'
  }

  # Methods
  def doctor
    clinic_doctor.doctor
  end

  def clinic
    clinic_doctor.clinic
  end

  def total_amount_to_pay
    commission_amount
  end
end

3.3 Service Classes

RevenueShareEngine Service

# parts/billing/services/revenue_share_engine.rb
class RevenueShareEngine < BaseInteractor
  def call
    validate_inputs
    load_configurations
    calculate_distributions
    create_revenue_shares
    process_approvals if approval_required?
    notify_stakeholders
  end

  private

  def validate_inputs
    fail!('Clinic ID is required') unless context.clinic_id.present?
    fail!('Billing payment ID is required') unless context.billing_payment_id.present?

    @clinic = Clinic.find_by(id: context.clinic_id)
    fail!('Clinic not found') unless @clinic

    @billing_payment = BillingPayment.find_by(id: context.billing_payment_id)
    fail!('Billing payment not found') unless @billing_payment
  end

  def load_configurations
    @configurations = RevenueShareConfiguration
                       .active
                       .for_clinic(@clinic.id)
                       .effective_on(Date.current)
                       .includes(:revenue_share_stakeholders)

    fail!('No active revenue sharing configurations found') if @configurations.empty?
  end

  def calculate_distributions
    @distributions = []

    @configurations.each do |config|
      config.revenue_share_stakeholders.active.each do |stakeholder|
        amount = calculate_stakeholder_amount(stakeholder)
        next unless amount > 0

        @distributions << {
          stakeholder: stakeholder,
          amount: amount,
          percentage: calculate_percentage(amount),
          config: config
        }
      end
    end
  end

  def calculate_stakeholder_amount(stakeholder)
    base_amount = @billing_payment.amount

    if stakeholder.share_percentage.present?
      calculated = (base_amount * stakeholder.share_percentage / 100).round(2)
    else
      calculated = stakeholder.share_amount
    end

    # Apply minimum and maximum constraints
    calculated = [calculated, stakeholder.minimum_amount].max if stakeholder.minimum_amount > 0
    calculated = [calculated, stakeholder.maximum_amount].min if stakeholder.maximum_amount.present?

    calculated
  end

  def calculate_percentage(amount)
    (amount / @billing_payment.amount * 100).round(2)
  end

  def create_revenue_shares
    @revenue_shares = []

    @distributions.each do |dist|
      revenue_share = RevenueShare.create!(
        billing_payment: @billing_payment,
        stakeholder: dist[:stakeholder],
        amount: dist[:amount],
        percentage: dist[:percentage],
        calculation_data: {
          rule_applied: dist[:config].configuration_name,
          base_amount: @billing_payment.amount,
          calculation_method: dist[:stakeholder].share_percentage ? 'percentage' : 'fixed_amount',
          calculated_at: Time.current
        },
        status: approval_required? ? 'pending' : 'approved'
      )

      @revenue_shares << revenue_share

      # Log audit trail
      AuditTrailService.call(
        entity: revenue_share,
        action: 'create',
        user: context.current_user,
        metadata: { billing_payment_id: @billing_payment.id }
      )
    end

    context.revenue_shares = @revenue_shares
  end

  def approval_required?
    @configurations.any?(&:approval_required)
  end

  def process_approvals
    @revenue_shares.each do |revenue_share|
      ApprovalWorkflowService.call(
        entity: revenue_share,
        entity_type: 'revenue_share',
        workflow_config: default_approval_workflow
      )
    end
  end

  def default_approval_workflow
    {
      steps: [
        {
          step: 1,
          approver_roles: ['clinic_owner', 'admin'],
          required_approvals: 1
        }
      ]
    }
  end

  def notify_stakeholders
    @revenue_shares.each do |revenue_share|
      NotificationService.call(
        recipient: revenue_share.stakeholder.user,
        type: 'revenue_share_calculated',
        data: {
          amount: revenue_share.amount,
          billing_payment_id: @billing_payment.id,
          status: revenue_share.status
        }
      )
    end
  end
end

CommissionCalculationEngine Service

# parts/billing/services/commission_calculation_engine.rb
class CommissionCalculationEngine < BaseInteractor
  def call
    validate_inputs
    load_commission_rules
    calculate_commissions
    create_calculations
    process_approvals if approval_required?
    notify_doctors
  end

  private

  def validate_inputs
    fail!('Clinic doctor ID is required') unless context.clinic_doctor_id.present?

    @clinic_doctor = ClinicDoctor.find_by(id: context.clinic_doctor_id)
    fail!('Clinic doctor not found') unless @clinic_doctor

    @calculation_period = context.calculation_period || 'monthly'
    @period_start = context.period_start || 1.month.ago.beginning_of_month
    @period_end = context.period_end || 1.month.ago.end_of_month
  end

  def load_commission_rules
    @commission_rules = CommissionRule
                         .active
                         .for_clinic(@clinic_doctor.clinic_id)
                         .effective_on(@period_end)

    fail!('No active commission rules found') if @commission_rules.empty?
  end

  def calculate_commissions
    @calculations = []

    @commission_rules.each do |rule|
      case rule.rule_type
      when 'percentage'
        calculate_percentage_commission(rule)
      when 'fixed_amount'
        calculate_fixed_amount_commission(rule)
      when 'tiered'
        calculate_tiered_commission(rule)
      when 'performance_bonus'
        calculate_performance_bonus(rule)
      end
    end
  end

  def calculate_percentage_commission(rule)
    base_amount = get_base_amount_for_rule(rule)
    return unless base_amount > 0

    percentage = rule.calculation_method['percentage'] || 0
    commission_amount = (base_amount * percentage / 100).round(2)

    @calculations << {
      rule: rule,
      base_amount: base_amount,
      commission_amount: commission_amount,
      calculation_details: {
        calculation_type: 'percentage',
        percentage_applied: percentage,
        period_start: @period_start,
        period_end: @period_end
      }
    }
  end

  def calculate_fixed_amount_commission(rule)
    qualifying_count = get_qualifying_count_for_rule(rule)
    return unless qualifying_count > 0

    amount_per_unit = rule.calculation_method['amount_per_unit'] || 0
    commission_amount = (qualifying_count * amount_per_unit).round(2)

    @calculations << {
      rule: rule,
      base_amount: qualifying_count,
      commission_amount: commission_amount,
      calculation_details: {
        calculation_type: 'fixed_amount',
        qualifying_count: qualifying_count,
        amount_per_unit: amount_per_unit,
        period_start: @period_start,
        period_end: @period_end
      }
    }
  end

  def calculate_tiered_commission(rule)
    base_amount = get_base_amount_for_rule(rule)
    return unless base_amount > 0

    tiers = rule.calculation_method['tiers'] || []
    commission_amount = 0
    remaining_amount = base_amount

    tiers.each do |tier|
      tier_min = tier['min_amount'] || 0
      tier_max = tier['max_amount']
      tier_percentage = tier['percentage'] || 0

      next if remaining_amount <= 0 || base_amount < tier_min

      applicable_amount = if tier_max.present?
                           [remaining_amount, tier_max - tier_min].min
                         else
                           remaining_amount
                         end

      tier_commission = (applicable_amount * tier_percentage / 100).round(2)
      commission_amount += tier_commission
      remaining_amount -= applicable_amount
    end

    @calculations << {
      rule: rule,
      base_amount: base_amount,
      commission_amount: commission_amount,
      calculation_details: {
        calculation_type: 'tiered',
        tiers_applied: tiers,
        period_start: @period_start,
        period_end: @period_end
      }
    }
  end

  def calculate_performance_bonus(rule)
    performance_metrics = get_performance_metrics_for_doctor
    return unless performance_metrics.any?

    bonus_amount = 0
    performance_details = {}

    rule.calculation_method['performance_criteria'].each do |criteria|
      metric_type = criteria['metric_type']
      threshold = criteria['threshold']
      bonus_per_unit = criteria['bonus_amount']

      actual_value = performance_metrics[metric_type]
      next unless actual_value && actual_value >= threshold

      bonus_amount += bonus_per_unit
      performance_details[metric_type] = {
        actual: actual_value,
        threshold: threshold,
        bonus_earned: bonus_per_unit
      }
    end

    return unless bonus_amount > 0

    @calculations << {
      rule: rule,
      base_amount: bonus_amount,
      commission_amount: bonus_amount,
      calculation_details: {
        calculation_type: 'performance_bonus',
        performance_metrics: performance_details,
        period_start: @period_start,
        period_end: @period_end
      }
    }
  end

  def get_base_amount_for_rule(rule)
    case rule.target_entity
    when 'consultation'
      get_consultation_revenue
    when 'service'
      get_service_revenue
    when 'revenue'
      get_total_revenue
    else
      0
    end
  end

  def get_qualifying_count_for_rule(rule)
    case rule.target_entity
    when 'consultation'
      get_consultation_count
    when 'service'
      get_service_count
    else
      0
    end
  end

  def get_consultation_revenue
    Appointment.joins(:billing_payments)
               .where(clinic_doctor: @clinic_doctor)
               .where(created_at: @period_start..@period_end)
               .sum('billing_payments.amount')
  end

  def get_consultation_count
    Appointment.where(clinic_doctor: @clinic_doctor)
               .where(created_at: @period_start..@period_end)
               .where(status: 'completed')
               .count
  end

  def get_service_revenue
    ClinicPatientService.joins(:billing_payments)
                        .where(clinic_id: @clinic_doctor.clinic_id)
                        .where(created_at: @period_start..@period_end)
                        .sum('billing_payments.amount')
  end

  def get_service_count
    ClinicPatientService.where(clinic_id: @clinic_doctor.clinic_id)
                        .where(created_at: @period_start..@period_end)
                        .count
  end

  def get_total_revenue
    BillingPayment.where(clinic_id: @clinic_doctor.clinic_id)
                  .where(created_at: @period_start..@period_end)
                  .sum(:amount)
  end

  def get_performance_metrics_for_doctor
    # This would integrate with existing performance tracking
    # For now, return mock data
    {
      'patient_satisfaction' => 4.5,
      'consultation_completion_rate' => 95.0,
      'revenue_target_achievement' => 120.0
    }
  end

  def create_calculations
    @commission_calculations = []

    @calculations.each do |calc|
      commission_calculation = CommissionCalculation.create!(
        commission_rule: calc[:rule],
        clinic_doctor: @clinic_doctor,
        base_amount: calc[:base_amount],
        commission_amount: calc[:commission_amount],
        calculation_details: calc[:calculation_details],
        calculation_period: @calculation_period,
        status: 'calculated',
        calculated_at: Time.current
      )

      @commission_calculations << commission_calculation

      # Log audit trail
      AuditTrailService.call(
        entity: commission_calculation,
        action: 'create',
        user: context.current_user,
        metadata: { clinic_doctor_id: @clinic_doctor.id, period: @calculation_period }
      )
    end

    context.commission_calculations = @commission_calculations
  end

  def approval_required?
    @commission_calculations.any? { |calc| calc.commission_amount > 1000 } # Example threshold
  end

  def process_approvals
    @commission_calculations.each do |calculation|
      ApprovalWorkflowService.call(
        entity: calculation,
        entity_type: 'commission_calculation',
        workflow_config: default_approval_workflow
      )
    end
  end

  def default_approval_workflow
    {
      steps: [
        {
          step: 1,
          approver_roles: ['clinic_owner', 'admin'],
          required_approvals: 1
        }
      ]
    }
  end

  def notify_doctors
    NotificationService.call(
      recipient: @clinic_doctor.doctor.user,
      type: 'commission_calculated',
      data: {
        total_amount: @commission_calculations.sum(&:commission_amount),
        calculation_count: @commission_calculations.count,
        period: @calculation_period
      }
    )
  end
end

4. API Implementation

4.1 Revenue Sharing APIs

Revenue Share Configuration Controller

# parts/billing/controllers/revenue_share_configurations_controller.rb
class RevenueShareConfigurationsController < ApplicationController
  before_action :authenticate_user!
  before_action :set_clinic
  before_action :set_configuration, only: [:show, :update, :destroy, :activate, :deactivate]

  # GET /api/clinics/:clinic_id/revenue_share_configurations
  def index
    @configurations = @clinic.revenue_share_configurations
                             .includes(:revenue_share_stakeholders, :created_by_user)
                             .order(created_at: :desc)

    render json: {
      data: @configurations.map { |config| configuration_json(config) },
      meta: pagination_meta(@configurations)
    }
  end

  # GET /api/clinics/:clinic_id/revenue_share_configurations/:id
  def show
    render json: {
      data: configuration_json(@configuration, include_stakeholders: true)
    }
  end

  # POST /api/clinics/:clinic_id/revenue_share_configurations
  def create
    @configuration = @clinic.revenue_share_configurations.build(configuration_params)
    @configuration.created_by_user = current_user

    if @configuration.save
      create_stakeholders if params[:stakeholders].present?

      render json: {
        data: configuration_json(@configuration, include_stakeholders: true),
        message: 'Revenue sharing configuration created successfully'
      }, status: :created
    else
      render json: {
        errors: @configuration.errors.full_messages
      }, status: :unprocessable_entity
    end
  end

  # PUT /api/clinics/:clinic_id/revenue_share_configurations/:id
  def update
    if @configuration.update(configuration_params)
      update_stakeholders if params[:stakeholders].present?

      render json: {
        data: configuration_json(@configuration, include_stakeholders: true),
        message: 'Revenue sharing configuration updated successfully'
      }
    else
      render json: {
        errors: @configuration.errors.full_messages
      }, status: :unprocessable_entity
    end
  end

  # DELETE /api/clinics/:clinic_id/revenue_share_configurations/:id
  def destroy
    if @configuration.destroy
      render json: {
        message: 'Revenue sharing configuration deleted successfully'
      }
    else
      render json: {
        errors: ['Configuration could not be deleted']
      }, status: :unprocessable_entity
    end
  end

  # POST /api/clinics/:clinic_id/revenue_share_configurations/:id/activate
  def activate
    if @configuration.update(status: 'active')
      render json: {
        data: configuration_json(@configuration),
        message: 'Configuration activated successfully'
      }
    else
      render json: {
        errors: @configuration.errors.full_messages
      }, status: :unprocessable_entity
    end
  end

  # POST /api/clinics/:clinic_id/revenue_share_configurations/:id/deactivate
  def deactivate
    if @configuration.update(status: 'inactive')
      render json: {
        data: configuration_json(@configuration),
        message: 'Configuration deactivated successfully'
      }
    else
      render json: {
        errors: @configuration.errors.full_messages
      }, status: :unprocessable_entity
    end
  end

  private

  def set_clinic
    @clinic = current_user.clinic_group&.clinics&.find(params[:clinic_id])

    unless @clinic
      render json: { error: 'Clinic not found or access denied' }, status: :not_found
      return
    end
  end

  def set_configuration
    @configuration = @clinic.revenue_share_configurations.find(params[:id])
  rescue ActiveRecord::RecordNotFound
    render json: { error: 'Configuration not found' }, status: :not_found
  end

  def configuration_params
    params.require(:revenue_share_configuration).permit(
      :configuration_name,
      :description,
      :approval_required,
      :effective_from,
      :effective_until,
      :status,
      distribution_rules: {}
    )
  end

  def configuration_json(config, include_stakeholders: false)
    json = {
      id: config.id,
      configuration_name: config.configuration_name,
      description: config.description,
      distribution_rules: config.distribution_rules,
      approval_required: config.approval_required,
      effective_from: config.effective_from,
      effective_until: config.effective_until,
      status: config.status,
      created_by: {
        id: config.created_by_user.id,
        name: config.created_by_user.title
      },
      created_at: config.created_at,
      updated_at: config.updated_at
    }

    if include_stakeholders
      json[:stakeholders] = config.revenue_share_stakeholders.active.map do |stakeholder|
        {
          id: stakeholder.id,
          user_id: stakeholder.user_id,
          user_name: stakeholder.user.title,
          stakeholder_type: stakeholder.stakeholder_type,
          share_percentage: stakeholder.share_percentage,
          share_amount: stakeholder.share_amount,
          minimum_amount: stakeholder.minimum_amount,
          maximum_amount: stakeholder.maximum_amount,
          payout_method: stakeholder.payout_method,
          active: stakeholder.active
        }
      end
    end

    json
  end

  def create_stakeholders
    params[:stakeholders].each do |stakeholder_params|
      @configuration.revenue_share_stakeholders.create!(
        user_id: stakeholder_params[:user_id],
        stakeholder_type: stakeholder_params[:stakeholder_type],
        share_percentage: stakeholder_params[:share_percentage],
        share_amount: stakeholder_params[:share_amount],
        minimum_amount: stakeholder_params[:minimum_amount] || 0,
        maximum_amount: stakeholder_params[:maximum_amount],
        payout_method: stakeholder_params[:payout_method] || 'manual',
        conditions: stakeholder_params[:conditions] || {}
      )
    end
  end

  def update_stakeholders
    # Remove existing stakeholders not in the update
    existing_ids = @configuration.revenue_share_stakeholders.pluck(:id)
    new_ids = params[:stakeholders].filter_map { |s| s[:id] if s[:id].present? }
    ids_to_remove = existing_ids - new_ids

    @configuration.revenue_share_stakeholders.where(id: ids_to_remove).destroy_all

    # Update or create stakeholders
    params[:stakeholders].each do |stakeholder_params|
      if stakeholder_params[:id].present?
        stakeholder = @configuration.revenue_share_stakeholders.find(stakeholder_params[:id])
        stakeholder.update!(stakeholder_update_params(stakeholder_params))
      else
        @configuration.revenue_share_stakeholders.create!(stakeholder_update_params(stakeholder_params))
      end
    end
  end

  def stakeholder_update_params(params)
    params.permit(
      :user_id,
      :stakeholder_type,
      :share_percentage,
      :share_amount,
      :minimum_amount,
      :maximum_amount,
      :payout_method,
      :active,
      conditions: {}
    )
  end
end

4.2 Commission APIs

Commission Rules Controller

# parts/billing/controllers/commission_rules_controller.rb
class CommissionRulesController < ApplicationController
  before_action :authenticate_user!
  before_action :set_clinic
  before_action :set_commission_rule, only: [:show, :update, :destroy, :activate, :deactivate]

  # GET /api/clinics/:clinic_id/commission_rules
  def index
    @rules = @clinic.commission_rules
                   .includes(:created_by_user)
                   .order(created_at: :desc)

    render json: {
      data: @rules.map { |rule| commission_rule_json(rule) },
      meta: pagination_meta(@rules)
    }
  end

  # GET /api/clinics/:clinic_id/commission_rules/:id
  def show
    render json: {
      data: commission_rule_json(@commission_rule)
    }
  end

  # POST /api/clinics/:clinic_id/commission_rules
  def create
    @commission_rule = @clinic.commission_rules.build(commission_rule_params)
    @commission_rule.created_by_user = current_user

    if @commission_rule.save
      render json: {
        data: commission_rule_json(@commission_rule),
        message: 'Commission rule created successfully'
      }, status: :created
    else
      render json: {
        errors: @commission_rule.errors.full_messages
      }, status: :unprocessable_entity
    end
  end

  # PUT /api/clinics/:clinic_id/commission_rules/:id
  def update
    if @commission_rule.update(commission_rule_params)
      render json: {
        data: commission_rule_json(@commission_rule),
        message: 'Commission rule updated successfully'
      }
    else
      render json: {
        errors: @commission_rule.errors.full_messages
      }, status: :unprocessable_entity
    end
  end

  # DELETE /api/clinics/:clinic_id/commission_rules/:id
  def destroy
    if @commission_rule.destroy
      render json: {
        message: 'Commission rule deleted successfully'
      }
    else
      render json: {
        errors: ['Rule could not be deleted']
      }, status: :unprocessable_entity
    end
  end

  # POST /api/clinics/:clinic_id/commission_rules/:id/calculate
  def calculate
    result = CommissionCalculationEngine.call(
      clinic_doctor_id: params[:clinic_doctor_id],
      calculation_period: params[:period] || 'monthly',
      period_start: params[:period_start],
      period_end: params[:period_end],
      current_user: current_user
    )

    if result.success?
      render json: {
        data: result.commission_calculations.map { |calc| commission_calculation_json(calc) },
        message: 'Commission calculated successfully'
      }
    else
      render json: {
        errors: [result.message]
      }, status: :unprocessable_entity
    end
  end

  private

  def set_clinic
    @clinic = current_user.clinic_group&.clinics&.find(params[:clinic_id])

    unless @clinic
      render json: { error: 'Clinic not found or access denied' }, status: :not_found
      return
    end
  end

  def set_commission_rule
    @commission_rule = @clinic.commission_rules.find(params[:id])
  rescue ActiveRecord::RecordNotFound
    render json: { error: 'Commission rule not found' }, status: :not_found
  end

  def commission_rule_params
    params.require(:commission_rule).permit(
      :rule_name,
      :rule_type,
      :target_entity,
      :effective_from,
      :effective_until,
      :active,
      conditions: {},
      calculation_method: {}
    )
  end

  def commission_rule_json(rule)
    {
      id: rule.id,
      rule_name: rule.rule_name,
      rule_type: rule.rule_type,
      target_entity: rule.target_entity,
      conditions: rule.conditions,
      calculation_method: rule.calculation_method,
      effective_from: rule.effective_from,
      effective_until: rule.effective_until,
      active: rule.active,
      created_by: {
        id: rule.created_by_user.id,
        name: rule.created_by_user.title
      },
      created_at: rule.created_at,
      updated_at: rule.updated_at
    }
  end

  def commission_calculation_json(calc)
    {
      id: calc.id,
      commission_rule: {
        id: calc.commission_rule.id,
        name: calc.commission_rule.rule_name
      },
      doctor: {
        id: calc.clinic_doctor.doctor.id,
        name: calc.clinic_doctor.doctor.user.title
      },
      base_amount: calc.base_amount,
      commission_amount: calc.commission_amount,
      calculation_details: calc.calculation_details,
      calculation_period: calc.calculation_period,
      status: calc.status,
      calculated_at: calc.calculated_at
    }
  end
end

5. User Interface Components

5.1 Dashboard Components

Revenue Sharing Dashboard Workflow

5.2 Configuration Interface Workflow

Commission Rule Setup Workflow

6. Background Jobs & Processing

6.1 Job Classes

Revenue Distribution Job

# parts/billing/jobs/revenue_distribution_job.rb
class RevenueDistributionJob < ApplicationJob
  queue_as :financial_processing

  def perform(billing_payment_id, options = {})
    @billing_payment = BillingPayment.find(billing_payment_id)
    @clinic = @billing_payment.clinic
    @options = options.with_indifferent_access

    Rails.logger.info "Starting revenue distribution for payment #{billing_payment_id}"

    result = RevenueShareEngine.call(
      clinic_id: @clinic.id,
      billing_payment_id: billing_payment_id,
      current_user: system_user
    )

    if result.success?
      process_automatic_payouts(result.revenue_shares) if @options[:auto_payout]
      send_notifications(result.revenue_shares)
      Rails.logger.info "Revenue distribution completed successfully"
    else
      Rails.logger.error "Revenue distribution failed: #{result.message}"
      raise StandardError, result.message
    end
  end

  private

  def process_automatic_payouts(revenue_shares)
    revenue_shares.each do |revenue_share|
      next unless revenue_share.stakeholder.payout_method == 'razorpay'
      next unless revenue_share.status == 'approved'

      PaymentProcessorService.call(
        revenue_share: revenue_share,
        payment_method: 'razorpay'
      )
    end
  end

  def send_notifications(revenue_shares)
    revenue_shares.each do |revenue_share|
      NotificationService.call(
        recipient: revenue_share.stakeholder.user,
        type: 'revenue_share_distributed',
        data: {
          amount: revenue_share.amount,
          clinic_name: @clinic.name,
          payment_date: @billing_payment.created_at
        }
      )
    end
  end

  def system_user
    @system_user ||= User.find_by(email: 'system@kulcare.com') || User.first
  end
end

Monthly Commission Calculation Job

# parts/billing/jobs/monthly_commission_calculation_job.rb
class MonthlyCommissionCalculationJob < ApplicationJob
  queue_as :financial_processing

  def perform(clinic_id, month = nil, year = nil)
    @clinic = Clinic.find(clinic_id)
    @month = month || Date.current.last_month.month
    @year = year || Date.current.last_month.year
    @period_start = Date.new(@year, @month, 1).beginning_of_month
    @period_end = @period_start.end_of_month

    Rails.logger.info "Starting monthly commission calculation for clinic #{clinic_id}, period: #{@period_start} - #{@period_end}"

    calculate_commissions_for_doctors
    generate_monthly_reports
    send_summary_notifications

    Rails.logger.info "Monthly commission calculation completed"
  end

  private

  def calculate_commissions_for_doctors
    @clinic.clinic_doctors.active.find_each do |clinic_doctor|
      result = CommissionCalculationEngine.call(
        clinic_doctor_id: clinic_doctor.id,
        calculation_period: 'monthly',
        period_start: @period_start,
        period_end: @period_end,
        current_user: system_user
      )

      unless result.success?
        Rails.logger.error "Commission calculation failed for doctor #{clinic_doctor.id}: #{result.message}"
      end
    end
  end

  def generate_monthly_reports
    ReportGenerationService.call(
      clinic: @clinic,
      report_type: 'monthly_commission_summary',
      period_start: @period_start,
      period_end: @period_end
    )
  end

  def send_summary_notifications
    total_commissions = CommissionCalculation
                         .joins(:clinic_doctor)
                         .where(clinic_doctors: { clinic_id: @clinic.id })
                         .where(calculated_at: @period_start..@period_end)
                         .sum(:commission_amount)

    NotificationService.call(
      recipient: @clinic.clinic_group.user,
      type: 'monthly_commission_summary',
      data: {
        clinic_name: @clinic.name,
        total_amount: total_commissions,
        period: "#{Date::MONTHNAMES[@month]} #{@year}",
        doctor_count: @clinic.clinic_doctors.active.count
      }
    )
  end

  def system_user
    @system_user ||= User.find_by(email: 'system@kulcare.com') || User.first
  end
end

7. Integration Specifications

7.1 Notification System Integration

SMS Notification Adapter

# parts/billing/services/sms_notification_adapter.rb
class SMSNotificationAdapter
  def self.send_revenue_share_notification(user, data)
    message = build_revenue_share_message(data)

    TwilioService.send_sms(
      to: user.complete_phone_number,
      message: message
    )
  end

  def self.send_commission_notification(user, data)
    message = build_commission_message(data)

    TwilioService.send_sms(
      to: user.complete_phone_number,
      message: message
    )
  end

  private

  def self.build_revenue_share_message(data)
    "Revenue Share Update: You've received ₹#{data[:amount]} from #{data[:clinic_name]}. " \
    "Payment processed on #{data[:payment_date].strftime('%d %b %Y')}. - Kulcare"
  end

  def self.build_commission_message(data)
    "Commission Update: Your #{data[:period]} commission of ₹#{data[:total_amount]} has been calculated. " \
    "Login to view details. - Kulcare"
  end
end

Email Notification Service

# parts/billing/services/email_notification_service.rb
class EmailNotificationService
  def self.send_revenue_share_report(user, revenue_shares, period)
    RevenueShareMailer.monthly_report(user, revenue_shares, period).deliver_now
  end

  def self.send_commission_statement(user, commissions, period)
    CommissionMailer.monthly_statement(user, commissions, period).deliver_now
  end

  def self.send_approval_notification(approver, entity)
    ApprovalMailer.pending_approval(approver, entity).deliver_now
  end
end

7.2 External Accounting System Integration

QuickBooks Integration Adapter

# parts/billing/services/quickbooks_integration_adapter.rb
class QuickBooksIntegrationAdapter
  def initialize(clinic)
    @clinic = clinic
    @qb_client = QuickBooksClient.new(@clinic.quickbooks_credentials)
  end

  def sync_revenue_shares(revenue_shares)
    revenue_shares.each do |revenue_share|
      create_journal_entry(revenue_share)
    end
  end

  def sync_commission_payments(commission_payments)
    commission_payments.each do |payment|
      create_expense_entry(payment)
    end
  end

  private

  def create_journal_entry(revenue_share)
    entry_data = {
      TxnDate: revenue_share.distributed_at.strftime('%Y-%m-%d'),
      Line: [
        {
          DetailType: 'JournalEntryLineDetail',
          Amount: revenue_share.amount,
          JournalEntryLineDetail: {
            PostingType: 'Debit',
            AccountRef: { value: revenue_sharing_expense_account }
          },
          Description: "Revenue share to #{revenue_share.stakeholder.user.title}"
        },
        {
          DetailType: 'JournalEntryLineDetail',
          Amount: revenue_share.amount,
          JournalEntryLineDetail: {
            PostingType: 'Credit',
            AccountRef: { value: accounts_payable_account }
          },
          Description: "Revenue share liability"
        }
      ]
    }

    @qb_client.create(:journal_entry, entry_data)
  end

  def create_expense_entry(commission_payment)
    expense_data = {
      TxnDate: commission_payment.processed_at.strftime('%Y-%m-%d'),
      AccountRef: { value: commission_expense_account },
      Line: [
        {
          DetailType: 'AccountBasedExpenseLineDetail',
          Amount: commission_payment.amount,
          AccountBasedExpenseLineDetail: {
            AccountRef: { value: commission_expense_account }
          },
          Description: "Commission payment"
        }
      ],
      EntityRef: { value: get_vendor_ref(commission_payment.billing_account.user) }
    }

    @qb_client.create(:purchase, expense_data)
  end

  def revenue_sharing_expense_account
    @clinic.accounting_configuration['revenue_sharing_expense_account']
  end

  def accounts_payable_account
    @clinic.accounting_configuration['accounts_payable_account']
  end

  def commission_expense_account
    @clinic.accounting_configuration['commission_expense_account']
  end

  def get_vendor_ref(user)
    # Create or find vendor in QuickBooks
    vendor_data = {
      Name: user.title,
      CompanyName: user.clinic&.name || user.title
    }

    vendor = @qb_client.create(:vendor, vendor_data)
    vendor.Id
  end
end

7.3 Payment Gateway Integration

Razorpay Payout Integration

# parts/billing/services/razorpay_payout_service.rb
class RazorpayPayoutService
  def initialize
    @client = Razorpay::Client.new(
      Rails.application.credentials.razorpay_key_id,
      Rails.application.credentials.razorpay_key_secret
    )
  end

  def process_revenue_share_payout(revenue_share)
    payout_data = {
      account_number: get_account_number(revenue_share.stakeholder.billing_account),
      amount: (revenue_share.amount * 100).to_i, # Convert to paise
      currency: 'INR',
      mode: determine_payout_mode(revenue_share.stakeholder.billing_account),
      purpose: 'revenue_share',
      reference_id: "RS_#{revenue_share.id}",
      narration: "Revenue share payment from #{revenue_share.billing_payment.clinic.name}"
    }

    response = @client.payout.create(payout_data)

    if response['status'] == 'processing'
      revenue_share.update!(
        status: 'distributed',
        distributed_at: Time.current
      )

      # Log successful payout
      Rails.logger.info "Razorpay payout initiated for revenue share #{revenue_share.id}: #{response['id']}"

      response
    else
      raise StandardError, "Payout failed: #{response['error']}"
    end
  rescue StandardError => e
    Rails.logger.error "Razorpay payout failed for revenue share #{revenue_share.id}: #{e.message}"

    revenue_share.update!(status: 'failed')
    raise e
  end

  def process_commission_payout(commission_payment)
    payout_data = {
      account_number: get_account_number(commission_payment.billing_account),
      amount: (commission_payment.amount * 100).to_i,
      currency: 'INR',
      mode: determine_payout_mode(commission_payment.billing_account),
      purpose: 'commission',
      reference_id: "CP_#{commission_payment.id}",
      narration: "Commission payment"
    }

    response = @client.payout.create(payout_data)

    if response['status'] == 'processing'
      commission_payment.update!(
        payment_status: 'processing',
        payment_reference: response['id'],
        processed_at: Time.current
      )

      Rails.logger.info "Razorpay payout initiated for commission payment #{commission_payment.id}: #{response['id']}"

      response
    else
      raise StandardError, "Commission payout failed: #{response['error']}"
    end
  rescue StandardError => e
    Rails.logger.error "Razorpay commission payout failed for commission payment #{commission_payment.id}: #{e.message}"

    commission_payment.update!(payment_status: 'failed')
    raise e
  end

  private

  def get_account_number(billing_account)
    billing_account.razorpay_account_id
  end

  def determine_payout_mode(billing_account)
    # Default to IMPS for faster transfer
    'IMPS'
  end
end

8. Testing Strategy

8.1 Model Tests

# spec/models/revenue_share_configuration_spec.rb
RSpec.describe RevenueShareConfiguration, type: :model do
  describe 'associations' do
    it { should belong_to(:clinic) }
    it { should belong_to(:created_by_user).class_name('User') }
    it { should have_many(:revenue_share_stakeholders).dependent(:destroy) }
  end

  describe 'validations' do
    it { should validate_presence_of(:configuration_name) }
    it { should validate_presence_of(:effective_from) }
    it { should validate_inclusion_of(:status).in_array(%w[draft active inactive]) }

    context 'when effective_until is present' do
      it 'validates effective_until is after effective_from' do
        config = build(:revenue_share_configuration,
                      effective_from: 1.day.from_now,
                      effective_until: 1.day.ago)
        expect(config).not_to be_valid
        expect(config.errors[:effective_until]).to include('must be after effective_from')
      end
    end
  end

  describe 'scopes' do
    let!(:active_config) { create(:revenue_share_configuration, status: 'active') }
    let!(:inactive_config) { create(:revenue_share_configuration, status: 'inactive') }

    describe '.active' do
      it 'returns only active configurations' do
        expect(RevenueShareConfiguration.active).to contain_exactly(active_config)
      end
    end

    describe '.effective_on' do
      let!(:current_config) { create(:revenue_share_configuration,
                                   effective_from: 1.month.ago,
                                   effective_until: 1.month.from_now) }
      let!(:expired_config) { create(:revenue_share_configuration,
                                   effective_from: 2.months.ago,
                                   effective_until: 1.month.ago) }

      it 'returns configurations effective on given date' do
        expect(RevenueShareConfiguration.effective_on(Date.current))
          .to include(current_config)
          .and not_include(expired_config)
      end
    end
  end
end

8.2 Service Tests

# spec/services/revenue_share_engine_spec.rb
RSpec.describe RevenueShareEngine, type: :interactor do
  let(:clinic) { create(:clinic) }
  let(:billing_payment) { create(:billing_payment, amount: 1000.00, clinic: clinic) }
  let(:user) { create(:user) }
  let!(:configuration) { create(:revenue_share_configuration,
                               clinic: clinic,
                               status: 'active',
                               effective_from: 1.month.ago) }
  let!(:stakeholder1) { create(:revenue_share_stakeholder,
                              configuration: configuration,
                              share_percentage: 60.0) }
  let!(:stakeholder2) { create(:revenue_share_stakeholder,
                              configuration: configuration,
                              share_percentage: 40.0) }

  describe '.call' do
    context 'with valid inputs' do
      it 'successfully creates revenue shares' do
        result = described_class.call(
          clinic_id: clinic.id,
          billing_payment_id: billing_payment.id,
          current_user: user
        )

        expect(result).to be_success
        expect(result.revenue_shares).to have(2).items

        # Check distribution amounts
        expect(result.revenue_shares.sum(&:amount)).to eq(1000.00)

        # Check individual stakeholder amounts
        stakeholder1_share = result.revenue_shares.find { |rs| rs.stakeholder == stakeholder1 }
        expect(stakeholder1_share.amount).to eq(600.00)

        stakeholder2_share = result.revenue_shares.find { |rs| rs.stakeholder == stakeholder2 }
        expect(stakeholder2_share.amount).to eq(400.00)
      end

      it 'creates audit trail entries' do
        expect {
          described_class.call(
            clinic_id: clinic.id,
            billing_payment_id: billing_payment.id,
            current_user: user
          )
        }.to change(AuditTrail, :count).by(2)
      end
    end

    context 'with invalid inputs' do
      it 'fails when clinic is not found' do
        result = described_class.call(
          clinic_id: 999999,
          billing_payment_id: billing_payment.id,
          current_user: user
        )

        expect(result).to be_failure
        expect(result.message).to eq('Clinic not found')
      end

      it 'fails when no active configurations exist' do
        configuration.update!(status: 'inactive')

        result = described_class.call(
          clinic_id: clinic.id,
          billing_payment_id: billing_payment.id,
          current_user: user
        )

        expect(result).to be_failure
        expect(result.message).to eq('No active revenue sharing configurations found')
      end
    end
  end
end

8.3 Controller Tests

# spec/controllers/revenue_share_configurations_controller_spec.rb
RSpec.describe RevenueShareConfigurationsController, type: :controller do
  let(:user) { create(:user_with_clinic) }
  let(:clinic) { user.clinic }

  before { sign_in user }

  describe 'GET #index' do
    let!(:configurations) { create_list(:revenue_share_configuration, 3, clinic: clinic) }

    it 'returns all configurations for the clinic' do
      get :index, params: { clinic_id: clinic.id }

      expect(response).to have_http_status(:ok)
      json_response = JSON.parse(response.body)
      expect(json_response['data']).to have(3).items
    end
  end

  describe 'POST #create' do
    let(:valid_attributes) do
      {
        configuration_name: 'Test Configuration',
        description: 'Test description',
        distribution_rules: { rule_type: 'percentage' },
        effective_from: 1.day.from_now,
        status: 'draft',
        stakeholders: [
          {
            user_id: user.id,
            stakeholder_type: 'clinic_owner',
            share_percentage: 100.0,
            payout_method: 'manual'
          }
        ]
      }
    end

    context 'with valid parameters' do
      it 'creates a new configuration' do
        expect {
          post :create, params: {
            clinic_id: clinic.id,
            revenue_share_configuration: valid_attributes,
            stakeholders: valid_attributes[:stakeholders]
          }
        }.to change(RevenueShareConfiguration, :count).by(1)

        expect(response).to have_http_status(:created)
        json_response = JSON.parse(response.body)
        expect(json_response['message']).to eq('Revenue sharing configuration created successfully')
      end

      it 'creates associated stakeholders' do
        expect {
          post :create, params: {
            clinic_id: clinic.id,
            revenue_share_configuration: valid_attributes,
            stakeholders: valid_attributes[:stakeholders]
          }
        }.to change(RevenueShareStakeholder, :count).by(1)
      end
    end

    context 'with invalid parameters' do
      it 'returns validation errors' do
        post :create, params: {
          clinic_id: clinic.id,
          revenue_share_configuration: { configuration_name: '' }
        }

        expect(response).to have_http_status(:unprocessable_entity)
        json_response = JSON.parse(response.body)
        expect(json_response['errors']).to be_present
      end
    end
  end

  describe 'PUT #update' do
    let(:configuration) { create(:revenue_share_configuration, clinic: clinic) }
    let(:update_attributes) { { configuration_name: 'Updated Name' } }

    it 'updates the configuration' do
      put :update, params: {
        clinic_id: clinic.id,
        id: configuration.id,
        revenue_share_configuration: update_attributes
      }

      expect(response).to have_http_status(:ok)
      configuration.reload
      expect(configuration.configuration_name).to eq('Updated Name')
    end
  end

  describe 'DELETE #destroy' do
    let!(:configuration) { create(:revenue_share_configuration, clinic: clinic) }

    it 'deletes the configuration' do
      expect {
        delete :destroy, params: { clinic_id: clinic.id, id: configuration.id }
      }.to change(RevenueShareConfiguration, :count).by(-1)

      expect(response).to have_http_status(:ok)
    end
  end
end

8.4 Financial Accuracy Tests

# spec/financial_accuracy/commission_calculation_accuracy_spec.rb
RSpec.describe 'Commission Calculation Accuracy', type: :feature do
  describe 'percentage-based commission calculations' do
    let(:clinic) { create(:clinic) }
    let(:doctor) { create(:clinic_doctor, clinic: clinic) }
    let(:commission_rule) { create(:commission_rule,
                                 clinic: clinic,
                                 rule_type: 'percentage',
                                 calculation_method: { 'percentage' => 15.0 }) }

    it 'calculates commission accurately for various amounts' do
      test_cases = [
        { base_amount: 1000.00, expected_commission: 150.00 },
        { base_amount: 2500.50, expected_commission: 375.08 },
        { base_amount: 10000.00, expected_commission: 1500.00 },
        { base_amount: 999.99, expected_commission: 150.00 }
      ]

      test_cases.each do |test_case|
        # Create mock data for the test case
        create_billing_data(doctor, test_case[:base_amount])

        result = CommissionCalculationEngine.call(
          clinic_doctor_id: doctor.id,
          calculation_period: 'monthly'
        )

        expect(result).to be_success
        calculated_commission = result.commission_calculations.first.commission_amount
        expect(calculated_commission).to eq(test_case[:expected_commission])

        # Clean up for next test case
        cleanup_test_data
      end
    end
  end

  describe 'tiered commission calculations' do
    let(:clinic) { create(:clinic) }
    let(:doctor) { create(:clinic_doctor, clinic: clinic) }
    let(:tiered_rule) { create(:commission_rule,
                             clinic: clinic,
                             rule_type: 'tiered',
                             calculation_method: {
                               'tiers' => [
                                 { 'min_amount' => 0, 'max_amount' => 5000, 'percentage' => 10.0 },
                                 { 'min_amount' => 5000, 'max_amount' => 10000, 'percentage' => 15.0 },
                                 { 'min_amount' => 10000, 'percentage' => 20.0 }
                               ]
                             }) }

    it 'calculates tiered commissions accurately' do
      test_cases = [
        { base_amount: 3000.00, expected_commission: 300.00 }, # First tier only
        { base_amount: 7000.00, expected_commission: 800.00 }, # First + second tier
        { base_amount: 15000.00, expected_commission: 1750.00 } # All tiers
      ]

      test_cases.each do |test_case|
        create_billing_data(doctor, test_case[:base_amount])

        result = CommissionCalculationEngine.call(
          clinic_doctor_id: doctor.id,
          calculation_period: 'monthly'
        )

        calculated_commission = result.commission_calculations.first.commission_amount
        expect(calculated_commission).to eq(test_case[:expected_commission])

        cleanup_test_data
      end
    end
  end

  private

  def create_billing_data(doctor, amount)
    billing_payment = create(:billing_payment, amount: amount, clinic: doctor.clinic)
    create(:appointment, clinic_doctor: doctor, billing_payment: billing_payment)
  end

  def cleanup_test_data
    BillingPayment.destroy_all
    Appointment.destroy_all
    CommissionCalculation.destroy_all
  end
end

9. Deployment & Operations

9.1 Production Configuration

Environment Variables

# Revenue Sharing & Commission System Configuration
REVENUE_SHARING_ENABLED=true
COMMISSION_CALCULATION_ENABLED=true
AUTO_APPROVAL_THRESHOLD=1000.00
RAZORPAY_PAYOUT_ENABLED=true
NOTIFICATION_SYSTEM_ENABLED=true

# Audit & Compliance
AUDIT_TRAIL_RETENTION_DAYS=2555  # 7 years for financial records
COMPLIANCE_REPORTING_ENABLED=true
TAX_REPORTING_ENABLED=true

# Performance Settings
COMMISSION_CALCULATION_BATCH_SIZE=100
REVENUE_DISTRIBUTION_QUEUE_PRIORITY=high
BACKGROUND_JOB_RETRY_LIMIT=3

# Integration Settings
QUICKBOOKS_INTEGRATION_ENABLED=false
TALLY_INTEGRATION_ENABLED=false
SMS_NOTIFICATIONS_ENABLED=true
EMAIL_NOTIFICATIONS_ENABLED=true

Cron Jobs Configuration

# config/schedule.rb
every 1.day, at: '2:00 AM' do
  runner "RevenueDistributionJob.perform_later"
end

every 1.month, at: '3:00 AM' do
  runner "MonthlyCommissionCalculationJob.perform_later"
end

every 1.week, at: '4:00 AM' do
  runner "CommissionReportGenerationJob.perform_later"
end

every 1.day, at: '5:00 AM' do
  runner "AuditTrailComplianceCheckJob.perform_later"
end

# Cleanup old audit trails (keep 7 years for compliance)
every 1.month, at: '6:00 AM' do
  runner "AuditTrailCleanupJob.perform_later"
end

9.2 Monitoring & Alerts

Performance Monitoring

# parts/billing/services/performance_monitor.rb
class PerformanceMonitor
  def self.track_commission_calculation_time(clinic_doctor_id)
    start_time = Time.current

    yield if block_given?

    execution_time = Time.current - start_time

    if execution_time > 30.seconds
      Rails.logger.warn "Slow commission calculation detected for doctor #{clinic_doctor_id}: #{execution_time}s"

      # Send alert to operations team
      SlackNotifier.notify_slow_calculation(clinic_doctor_id, execution_time)
    end

    # Track metrics
    StatsD.timing('commission_calculation.duration', execution_time * 1000)
    StatsD.increment('commission_calculation.count')
  end

  def self.track_revenue_distribution_success_rate
    successful = RevenueShare.where(created_at: 1.hour.ago..Time.current)
                            .where(status: ['approved', 'distributed']).count
    failed = RevenueShare.where(created_at: 1.hour.ago..Time.current)
                        .where(status: 'failed').count

    total = successful + failed
    return if total == 0

    success_rate = (successful.to_f / total * 100).round(2)

    if success_rate < 95.0
      Rails.logger.error "Low revenue distribution success rate: #{success_rate}%"
      AlertManager.send_critical_alert(
        "Revenue Distribution Success Rate Alert",
        "Success rate dropped to #{success_rate}% in the last hour"
      )
    end

    StatsD.gauge('revenue_distribution.success_rate', success_rate)
  end
end

This implementation plan provides a comprehensive, production-ready solution that aligns with the PRD requirements and follows the current project's architecture patterns. All stakeholders are properly linked to users with appropriate roles, clinic_id is used consistently, and the system includes robust error handling, testing, and monitoring capabilities.

On this page

System Architecture Overview1. Database Schema Design1.1 Revenue Sharing TablesDatabase Schema Diagramrevenue_share_configurationsconfiguration_name (string, user-defined name)revenue_share_stakeholdersrevenue_shares1.2 Commission Management Tablescommission_rulescommission_calculationscommission_payments1.3 Workflow & Audit Tablesapproval_workflowsaudit_trails1.4 Reporting & Analytics Tablescustom_reportsscheduled_reportsuser_training_progress2. Migration Scripts2.1 Revenue Sharing Tables Migration2.2 Commission Tables Migration2.3 Workflow & Audit Tables Migration2.4 Reporting Tables Migration3. Business Logic Implementation3.1 Service Classes Architecture3.2 Core ModelsRevenueShareConfiguration ModelRevenueShareStakeholder ModelCommissionRule ModelCommissionCalculation Model3.3 Service ClassesRevenueShareEngine ServiceCommissionCalculationEngine Service4. API Implementation4.1 Revenue Sharing APIsRevenue Share Configuration Controller4.2 Commission APIsCommission Rules Controller5. User Interface Components5.1 Dashboard ComponentsRevenue Sharing Dashboard Workflow5.2 Configuration Interface WorkflowCommission Rule Setup Workflow6. Background Jobs & Processing6.1 Job ClassesRevenue Distribution JobMonthly Commission Calculation Job7. Integration Specifications7.1 Notification System IntegrationSMS Notification AdapterEmail Notification Service7.2 External Accounting System IntegrationQuickBooks Integration Adapter7.3 Payment Gateway IntegrationRazorpay Payout Integration8. Testing Strategy8.1 Model Tests8.2 Service Tests8.3 Controller Tests8.4 Financial Accuracy Tests9. Deployment & Operations9.1 Production ConfigurationEnvironment VariablesCron Jobs Configuration9.2 Monitoring & AlertsPerformance Monitoring