Расчет роялти на основе диапазонов в рельсах 3 - PullRequest
3 голосов
/ 18 января 2011

Я создал приложение, которое отслеживает продажи книг и, будем надеяться, рассчитает авторские гонорары.

Прямо сейчас я отслеживаю продажи в заказах. Каждый заказ имеет_много: line_items. При сохранении новой позиции я вычисляю общие продажи данного продукта, поэтому у меня есть общее количество продаж.

Каждый автор имеет несколько правил роялти, основанных на их контракте. Например, продано от 0 до 5000 копий, они получают 10 процентов. 5001 до 10000, они получают 20 процентов. Сначала я рассчитывал долю автора на каждую позицию. Это работало хорошо, но потом я понял, что мое приложение выбирает, какое правило роялти применять на основе общих продаж. Если я размещу большой заказ, возможно, что авторские гонорары будут рассчитываться по высокой ставке роялти для всей позиции, тогда как на самом деле роялти следует рассчитывать как на основе более низкой, так и высокой ставки роялти (как в одной строке). пункт толкает общий объем продаж, превышающий точку прорыва правила роялти).

Итак, мой вопрос, как лучше всего это сделать. Я исследовал использование диапазонов, но все это немного ново для меня, и код становится немного сложнее. Вот, по общему признанию, неуклюжий код, который я использую для переноса всех правил роялти для данного контракта в массив:

  def royalty_rate
    @product = Product.find_by_id(product_id)
    @total_sold = @product.total_sold
    @rules = Contract.find_by_product_id(@product).royalties
    ... where next?      
  end

@ rules имеет: нижний и верхний для каждого правила роялти, поэтому для этого продукта первое: нижнее будет 0, а верхнее будет 5000, затем второе нижнее будет 5001, а второе верхнее будет 10000 и т. Д.

Любая помощь или идеи с этим будут оценены. На самом деле это последний шаг для меня, чтобы получить полностью рабочую версию, с которой я могу играть.

Я использовал этот код ниже, чтобы выбрать конкретное правило, основанное на значении total_sold, но опять-таки, это дает эффект кумулятивных продаж и выбора самой высокой ставки роялти вместо их разделения.

    @rules = @contract.royalties.where("lower <= :total_sold AND upper >= :total_sold", {:total_sold => @total_sold}).limit(1)

Заранее спасибо.

Ответы [ 3 ]

3 голосов
/ 18 января 2011

Похоже, вам нужно хранить правила расчета роялти отдельно для каждого автора - или, возможно, у вас есть несколько схем, и каждый автор связан с одной из них?

Для первого случая, возможно, что-то вроде этого:

class Author
  has_many :royalty_rules
end

class RoyaltyRule
  belongs_to :author
  # columns :lower, :upper, :rate
end

Таким образом, когда автор добавляется, вы добавляете строки в модель RoyaltyRule для каждого уровня. Тогда вам нужен метод для расчета роялти

class Author
  def royalty(product)
    product = Product.find_by_id(product.id)
    units = product.total_sold
    amount = 0
    royalty_rules.each do |rule|
      case units
      when 0
      when Range.new(rule.lower,rule.upper)
        # reached the last applicable rule -- add the part falling within the tier
        amount += (units - rule.lower + 1) * rule.rate
        break
      else
        # add the full amount for the tier
        amount += (rule.upper - rule.lower + 1) * rule.rate
      end
    end
    amount
  end
end

И некоторые характеристики для тестирования:

describe Author do
  before(:each) do
    @author = Author.new
    @tier1 = mock('tier1',:lower=>1,:upper=>5000,:rate=>0.10)
    @tier2 = mock('tier2',:lower=>5001,:upper=>10000,:rate=>0.20)
    @tier3 = mock('tier3',:lower=>10001,:upper=>15000,:rate=>0.30)
    @author.stub(:royalty_rules) { [@tier1,@tier2,@tier3] }
  end

  it "should work for one tier" do
    product = mock('product',:total_sold=>1000)
    @author.royalty(product).should == 100
  end

  it "should work for two tiers" do
    product = mock('product',:total_sold=>8000)
    @author.royalty(product).should == (5000 * 0.10) + (3000 * 0.20)
  end

  it "should work for three tiers" do
    product = mock('product',:total_sold=>14000)
    @author.royalty(product).should == (5000 * 0.10) + (5000 * 0.20) + (4000 * 0.30)
  end

  # edge cases
  it "should be zero when units is zero" do
    product = mock('product',:total_sold=>0)
    @author.royalty(product).should == 0
  end

  it "should be 500 when units is 5000" do
    product = mock('product',:total_sold=>5000)
    @author.royalty(product).should == 500
  end

  it "should be 500.2 when units is 5001" do
    product = mock('product',:total_sold=>5001)
    @author.royalty(product).should == 500.2
  end
end

Примечания: Author.royalty_rules необходимо вернуть отсортированные уровни от низкого к высокому. Кроме того, самый нижний уровень начинается с 1 вместо 0 для упрощения расчета.

1 голос
/ 18 января 2011

Итак, я не понимаю, почему вы не можете просто рассчитать общее проданное количество?

Я предполагаю, что им не нужно знать точный момент заказа, так почему бы не рассчитать, основываясь на количестве, проданном по состоянию на вчерашний день.

Например, утром запускаем задачу rake (скажем), которая использует следующий модуль с именем RoyaltyPayments в файле в lib/royalty_payments.rb

сделать что-то вроде

Module RoyaltyPayments
def royalty_range(total_sold, product_price)
  sold_price = total_sold * product_price
  leftover = total_sold % 5000
  case total_sold
    when 0..5000
      total_sold * 0.1
    when 5001..10000
      ((sold_price * 0.10)*5000) + ((sold_price * 0.2)*leftover)
    else
      ((sold_price * 0.10)*5000) + (sold_price * 0.20)*5000) + ((sold_price * 0.3)*(total_sold - 10000)
  end
end

Тогда сделайте lib/tasks/royalty_payments.rake

В этот файл положить что-то вроде:

include RoyaltyPayments
namespace :royalty_payments
  desc "Make royalty calculations"
  task :calculate_latest_totals
    Product.all.each do |product|
    total_sold = product.total_sold
    royalty_range(total_sold, product.price)
  end

Нечто подобное.

0 голосов
/ 18 января 2011

Вы можете делать диапазоны переменных, например.min..max

$irb
> min = 0  
=> 0 
> max = 5000  
=> 5000
> min..max 
=> 0..5000 
> (min..max).class
=> Range

% - это Numeric.modulo, подробности см. http://www.ruby -doc.org / core / classes / Numeric.html # M000969 .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...