Это немного надуманная проблема, но реальная для нашей отрасли.
Наши клиенты арендуют гостиничные номера и по причинам, связанным с учетом отелей, плата за номер добавляется в фолио каждую ночь, а не все сразу. Я работал над исправлением нашего модуля расчета фиксированной ставки, который полностью сломан.
Пример проблемы: клиент соглашается заплатить 376 долларов США за 30 ночей проживания. Тем не менее, 376 долларов США не вносятся в книги 1 раз, они (376 долларов США / ночь) накладываются на фолио за каждую ночь проживания.
Таким образом, учитывая желаемую Итоговую сумму за весь период пребывания, количество ночей и ставку налога — как мы получим список промежуточных итогов, если их обложить налогом и свернуть, который будет равен Итоговому значению. Наше существующее решение заключается в том, что персонал отеля требует ручной настройки, что сопряжено с ошибками и обременительно. Мы хотели бы, чтобы цены распределялись как можно более равномерно, чтобы бухгалтерские отчеты были как можно более точными.
Сейчас это немного грязно, и я не могу получить его, чтобы получить Тотал с правильным количеством ночей. Я чувствую, что должен быть алгоритм для этого, и я просто не знаю об этом.
То, над чем я сейчас работаю, включает в себя эту хвостовую рекурсивную функцию,
function iterationRate($amount, $days, $tax, $ary=array()){
$amount_left = $amount;
// we have subtotals already, subtract these from the desired total
if(count($ary) > 0) {
$amount_left = bcsub($amount, sumWithTaxes($ary, $tax));
}
// make sure it's a valid currency amount
$amount_left = bcround($amount_left, 2);
$tonights_rate = bcdiv($amount_left/$tax, $days);
// prevent negative charges / credit
if ($tonights_rate >= 0) {
array_push($ary, bcround($tonights_rate, 2));
}
else {
// remove an item from the array to give us more space
array_shift($ary);
$days = $days + 1;
}
if($days > 1) {
return iterationRate($amount, $days - 1, $tax, $ary);
}
$test_subtotals = sumWithTaxes($ary, $tax);
$diff = bcsub($test_subtotals, $amount);
// if we don't have our amount, remove the diff from an item and try again
if(abs($diff) > 0) {
$firstnight = array_shift($ary);
array_push($ary, bcsub($firstnight, $diff));
return iterationRate($amount, $days, $tax, $ary);
}
return $ary;
}
Весь тест здесь: http://sandbox.onlinephpfunctions.com/code/9eb43e95fad866ef7198a0e2bfa778edf326e091
$ 300 за 3 ночи, ставка налога 16,25%
Поиск промежуточного итога по итогу и деление по ночам не сработает:
300 / (1 + налоговая ставка) = 258,0645 долл. США, округленное до 258,06 долл. США
258,06 долл. США / 3 = 86,02 долл. США
86,02 * налог на прибыль = 99,9993, округленный до 99,99.
99,99 * 3! = 300.
Итоговый массив, который делает работа, [86.02, 86.02, 86.04] 2 * (86,02 * налог) + (86,04 * налог) = всего
Разница между налог на сумму а также сумма налогов неизбежно Не сравнивайте это с другими несоответствиями (ценой).
Существуют правила порядка исчисления налога в зависимости от страны. Я думаю, что в большинстве стран оба метода разрешены, а различия округления допустимы в обоих направлениях — все равно нужно проверять. В этом случае это должен решить ваш клиент или даже лучше его бухгалтерия (вы можете спросить о правилах).
Если вы хотите быть точным в общем налоге, то рассчитайте его, используя сумму в качестве базы (также есть два метода, так как вы можете использовать для него сумму брутто или нетто) — только база расчета будет иметь точную сумму в своем столбце.
Если вы хотите быть точным в сумме всех столбцов, то вы потеряете точность общей ставки налога.
Подсказки:
Итак, я делал это слишком сложно. Я полагаю, что теперь это правильный способ сделать это, просто подсчитывая каждую ночь, пока мы не достигнем нужной суммы.
function iterationRate($amount, $days, $tax, $ary=array())
{
$nightly_base = floor(bcdiv(bcdiv($amount, $tax), $days));
$rates = [];
for ($i = 0; $i < $days; $i++) {
array_push($rates, $nightly_base);
}
$idx = 0;
do {
$rates[$idx] = $rates[$idx] + 0.01;
$idx = ( ($idx >= count($rates) - 1) ? 0 : ( $idx + 1 ) );
} while ($amount > sumWithTaxes($rates, $tax));
return $rates;
}
http://sandbox.onlinephpfunctions.com/code/735f05c2fa0cfa416533f674bee1b02b247f9dd6
Я бы сделал что-то более простое.
Функция вычисления делает работу. Остальное просто тест.
function calculate($amount, $days, $taxes){
$per_day = round($amount/$days,2);
$last_day = $amount-$per_day*($days-1);
$total_taxes = $amount-$amount/$taxes;
$per_day_taxes = round($per_day-$per_day/$taxes,2);
$last_day_taxes = $total_taxes-$per_day_taxes*($days-1);
return (object) array("per_day"=>$per_day,
"last_day"=>$last_day,
"per_day_taxes"=>$per_day_taxes,
"last_day_taxes"=>$last_day_taxes,
"total_taxes"=>$total_taxes);
}function runTests($tests) {
foreach($tests as $idx => $test) {
$values = calculate($test->amount,$test->days,$test->taxes);
printf("Test %d: Amount(%.02f) Days(%d) Tax(%.02f)\n", $idx, $test->amount, $test->days, $test->taxes);
printf("Rates the first %d days (incl. taxes):| %.02f, where taxes are:| %.02f\n", $test->days-1, $values->per_day, $values->per_day_taxes);
printf("Rate the last day (incl. taxes): | %.02f, where taxes is :| %.02f\n", $values->last_day, $values->last_day_taxes);
printf("Rates the first %d days (excl. taxes):| %.02f\n", $test->days-1, $values->per_day-$values->per_day_taxes);
printf("Rate the last day (excl. taxes): | %.02f\n", $values->last_day-$values->last_day_taxes);
printf("Total (excl. without) taxes: | %.02f\n", $test->amount-$values->total_taxes);
printf("Total taxes: | %.02f\n", $values->total_taxes);
echo "=======================\n";
echo "\n";
}
}$tests = [
(object) array("amount"=>376, "days"=>22, "taxes"=>1),
(object) array("amount"=>376, "days"=>22, "taxes"=>1.1625),
(object) array("amount"=>1505, "days"=>25, "taxes"=>1.09),
(object) array("amount"=>380, "days"=>22, "taxes"=>1.1),
];
runTests($tests);
Результат:
Test 0: Amount(376.00) Days(22) Tax(1.00)
Rates the first 21 days (incl. taxes):| 17.09, where taxes are:| 0.00
Rate the last day (incl. taxes): | 17.11, where taxes is :| 0.00
Rates the first 21 days (excl. taxes):| 17.09
Rate the last day (excl. taxes): | 17.11
Total (excl. without) taxes: | 376.00
Total taxes: | 0.00
=======================
Test 1: Amount(376.00) Days(22) Tax(1.16)
Rates the first 21 days (incl. taxes):| 17.09, where taxes are:| 2.39
Rate the last day (incl. taxes): | 17.11, where taxes is :| 2.37
Rates the first 21 days (excl. taxes):| 14.70
Rate the last day (excl. taxes): | 14.74
Total (excl. without) taxes: | 323.44
Total taxes: | 52.56
=======================
Test 2: Amount(1505.00) Days(25) Tax(1.09)
Rates the first 24 days (incl. taxes):| 60.20, where taxes are:| 4.97
Rate the last day (incl. taxes): | 60.20, where taxes is :| 4.99
Rates the first 24 days (excl. taxes):| 55.23
Rate the last day (excl. taxes): | 55.21
Total (excl. without) taxes: | 1380.73
Total taxes: | 124.27
=======================
Test 3: Amount(380.00) Days(22) Tax(1.10)
Rates the first 21 days (incl. taxes):| 17.27, where taxes are:| 1.57
Rate the last day (incl. taxes): | 17.33, where taxes is :| 1.58
Rates the first 21 days (excl. taxes):| 15.70
Rate the last day (excl. taxes): | 15.75
Total (excl. without) taxes: | 345.45
Total taxes: | 34.55
=======================