Commit 361df556 authored by Shani's avatar Shani

Merge branch 'dev' into 'dev'

Merge branch 'discount_proration' into dev

See merge request sdoc/billrun!2876
parents 4b8eaafc 3b8254c8
......@@ -1323,7 +1323,7 @@ class ConfigModel {
}, $customFields));
$additionalFields = array('computed');
if ($diff = array_diff($useFromStructure, array_merge($customFields, $billrunFields, $additionalFields))) {
throw new Exception('Unknown source field(s) ' . implode(',', $diff));
throw new Exception('Unknown source field(s) ' . implode(',', array_unique($diff)));
}
}
return true;
......
......@@ -24,6 +24,8 @@ billapi.discounts.create.update_parameters.7.name=discount_type
billapi.discounts.create.update_parameters.7.type=string
billapi.discounts.create.update_parameters.8.name=discount_subject
billapi.discounts.create.update_parameters.8.type=array
billapi.discounts.create.update_parameters.9.name=prorated
billapi.discounts.create.update_parameters.9.type=boolean
billapi.discounts.update.error_base=30100
billapi.discounts.update.permission="write"
billapi.discounts.update.unique_query_parameters=1
......@@ -46,6 +48,8 @@ billapi.discounts.update.update_parameters.7.name=discount_type
billapi.discounts.update.update_parameters.7.type=string
billapi.discounts.update.update_parameters.8.name=discount_subject
billapi.discounts.update.update_parameters.8.type=array
billapi.discounts.update.update_parameters.9.name=prorated
billapi.discounts.update.update_parameters.9.type=boolean
billapi.discounts.closeandnew.error_base=30200
billapi.discounts.closeandnew.permission="write"
billapi.discounts.closeandnew.unique_query_parameters=1
......@@ -68,6 +72,8 @@ billapi.discounts.closeandnew.update_parameters.7.name=discount_type
billapi.discounts.closeandnew.update_parameters.7.type=string
billapi.discounts.closeandnew.update_parameters.8.name=discount_subject
billapi.discounts.closeandnew.update_parameters.8.type=array
billapi.discounts.closeandnew.update_parameters.9.name=prorated
billapi.discounts.closeandnew.update_parameters.9.type=boolean
billapi.discounts.close.permission="write"
billapi.discounts.close.query_parameters.0.name=_id
billapi.discounts.close.query_parameters.0.type=dbid
......
......@@ -111,7 +111,8 @@ abstract class Billrun_Discount {
}
foreach ($this->discountData['discount_subject'] as $subjects) {
foreach ($subjects as $key => $val) {
if ($this->isMonetray()) {
$val = is_array($val) ? $val['value'] : $val; // Backward compatibility with amount/perecnt only discount subject.
if ($this->isMonetray()) {
$discountLine['discount'][$key]['value'] = -(abs($val)) * $lineModifier;
} else {
$discountLine['discount'][$key]['value'] = $val;
......@@ -160,8 +161,9 @@ abstract class Billrun_Discount {
return FALSE;
}
$charge = $totalPrice = 0;
$addedPricingData = [];
//discount each of the subject included in the discount
foreach ($totals['rates'] as $key => $ratePrice ) {
foreach (($totals['rates'] ?: []) as $key => $ratePrice ) {
if( empty($discount['discount'][$key]) && !$this->isApplyToAnySubject() ) {
Billrun_Factory::log('discount generated invoice totals that arer not in the discount subject',Zend_Log::WARN);
continue;
......@@ -175,7 +177,10 @@ abstract class Billrun_Discount {
} else {
$callback = array($this, 'calculatePricePercent');
}
$price = call_user_func_array($callback, array($ratePrice, $val, $discountLimit));
$simplePrice = call_user_func_array($callback, array($ratePrice, $val, $discountLimit));
$pricingData = $this->priceManipulation($simplePrice, $val, $key ,$discountLimit, $totals);
$addedPricingData[] = $pricingData;
$price = $pricingData['price'];
$taxationInfo = $this->getTaxationDataForPrice($price, $key, $discount);
$taxationInformation[] = $taxationInfo;
$totalPrice += $this->repriceForUpfront( $price, @$taxationInfo['tax_rate'], $discount, $invoice, $callback, $val, $ratePrice);
......@@ -185,7 +190,7 @@ abstract class Billrun_Discount {
$charge = $totalPrice > 0 ? $totalPrice : max($totalPrice, $discountLimit);
}
return array('price' => $charge, 'tax_info' => $taxationInformation);
return array('price' => $charge, 'tax_info' => $taxationInformation,'discount_pricing_data'=> $addedPricingData);
}
protected function getTaxationDataForPrice($price, $identifingKey, $discount) {
......@@ -204,6 +209,7 @@ abstract class Billrun_Discount {
$rateColl = Billrun_Factory::db()->getCollection($mapping['coll']);
$query = array_merge(array($mapping['key_field'] => $identifingKey), Billrun_Utils_Mongo::getDateBoundQuery($discount['urt']->sec));
//TODO this should use a cache! who programmed this huging function!.
$tmpRate = $rateColl->query($query)->cursor()->limit(1)->current();
if($tmpRate && !$tmpRate->isEmpty()) {
$rate = $tmpRate;
......@@ -402,6 +408,13 @@ abstract class Billrun_Discount {
//=================================== Protected ======================================
protected function priceManipulation($simpleDiscountPrice, $subjectValue, $subjectKey, $discountLimit ,$discount ) {
return [
'price' => max($min(0,simpleDiscountPrice),$discountLimit) ,
'pricing_breakdown' => [$subjectKey => [['base_price' => $simpleDiscountPrice]]]
];
}
/**
* Get Totals from the billrun object
* @param type $billrun
......
......@@ -43,16 +43,69 @@ class Billrun_Discount_Subscriber extends Billrun_Discount {
return FALSE;
}
//==================================== Protected ==========================================
protected function priceManipulation($simpleDiscountPrice, $subjectValue, $subjectKey, $discountLimit ,$totals ) {
$retPrice= $simpleDiscountPrice;
$pricingData = [];
if( !empty($this->discountData['discount_subject']['service'][$subjectKey]['operations']) ) {
foreach($this->discountData['discount_subject']['service'][$subjectKey]['operations'] as $operation) {
switch($operation['name']) {
case 'recurring_by_quantity':
//Multiply the discount amount by some intrval over the quantity of the service.
$quantityMultiplier = 0;
foreach($operation['params'] as $param) {
if(empty($param['value'])) { continue; }
$quantityMultiplier += floor($totals[$param['name']][($this->isApplyToAnySubject() ? 'total' : $subjectKey)] / $param['value']);
}
$pricingData[] = ['name' => 'recurring_by_quantity', 'multiplier' => $quantityMultiplier , 'base_price' => $retPrice ];
$retPrice = $retPrice * $quantityMultiplier;
break;
case 'unquantitative_amount':
//retrive the original service price form a quantitve service.
$quantityMultiplier = 0;
foreach($operation['params'] as $param) {
$quantityMultiplier += $totals[$param['name']][($this->isApplyToAnySubject() ? 'total' : $subjectKey)];
}
$pricingData[] = ['name' => 'dequtitive_amount', 'multiplier' => $quantityMultiplier , 'base_price' => $retPrice ];
$retPrice = $retPrice / $quantityMultiplier;
break;
}
}
}
return [ 'price' => max(min(0,$retPrice),$discountLimit) , 'pricing_breakdown' => [ $subjectKey => $pricingData] ];
}
protected function checkServiceEligiblity($subscriber, $accountInvoice) {
$eligible = !empty(@Billrun_Util::getFieldVal($this->discountData['params'], array()));
$multiplier = 1;
$startDate = $endDate = null;
$subscriberData = $subscriber->getData();
$addedData = array('aid' => $accountInvoice->getRawData()['aid'], 'sid' => $subscriberData['sid']);
$paramsQuery = $this->mapFlatArrayToStructure(@Billrun_Util::getFieldVal($this->discountData['params'],array()), $this->discountToQueryMapping);
$eligible &= Billrun_Utils_Arrayquery_Query::exists($subscriberData, $paramsQuery);
$cover = [ 'start' => new MongoDate($this->billrunStartDate), 'end' => new MongoDate($this->billrunDate-1) ];
if($eligible && !empty($this->discountData['prorated'])) {
$arrayArggregator = new Billrun_Utils_Arrayquery_Aggregate();
$matchedDocs = $arrayArggregator->aggregate([ ['$unwind' => '$breakdown.flat'],['$unwind' => '$breakdown.service'],['$project' => ['flat'=> ['$push'=>'$breakdown.flat'],'service'=>['$push'=>'$breakdown.service']]] ], [$subscriberData]);
foreach($matchedDocs as $matchedDoc ) {
foreach($matchedDoc as $matchedType ) {
foreach($matchedType as $matched) {
if(!empty($matched['start']) && $cover['start'] < $matched['start'] && $cover['end'] > $matched['start']) {
$cover['start'] = $matched['start'];
}
if(!empty($matched['end']) && $cover['end'] > $matched['end'] && $cover['start'] < $matched['end']) {
$cover['end'] = $matched['end'];
}
}
}
}
}
$startDate = $cover['start'];
$multiplier = !empty($this->discountData['prorated']) ? Billrun_Plan::getMonthsDiff(date('Ymd',$cover['start']->sec) ,date('Ymd',$cover['end']->sec)) : 1;
$endDate = $this->adjustDiscountDuration($accountInvoice->getRawData(), $multiplier, $subscriberData);
$ret = array(array_merge(array('modifier' => $multiplier, 'start' => $startDate, 'end' => $endDate), $addedData));
......@@ -112,6 +165,8 @@ class Billrun_Discount_Subscriber extends Billrun_Discount {
$usageTotals['after_vat'] += $usage['cost'];
$usageTotals['before_vat'] += $usage['cost'];
@$usageTotals['rates'][$usage['name']] += $usage['cost'];
@$usageTotals['quantity'][$usage['name']] += $usage['usagev'];
@$usageTotals['quantity']['total'] += $usage['usagev'];
@$usageTotals['sections'][$this->discountableSections[$section]] += $usage['cost'];
@$usageTotals['count'][$this->discountableSections[$section]] += $usage['usagev'];
}
......
......@@ -75,6 +75,7 @@ class Billrun_DiscountManager {
$pricingData = $discountInstances[$discountId]->calculatePriceAndTax($cdr, $invoice);
$cdr['aprice'] = $pricingData['price'];
$cdr['tax_info'] = $pricingData['tax_info'];
$cdr['pricing_data'] = $pricingData['discount_pricing_data'];
}
}
......@@ -227,8 +228,8 @@ class Billrun_DiscountManager {
$beforeVatTotals[$accountEntityId] = $invoiceObj->getTotals();
foreach ($discounts as $discountId => &$typeDiscounts) {
$instance = $discountInstances[$discountId];
foreach ($typeDiscounts as &$discount) {
$entityId = $instance->getEntityId($discount);
foreach ($typeDiscounts as &$discount) {
$entityId = $instance->getEntityId($discount);
if (!isset($entityTotals[$entityId])) {
$beforeVatTotals[$entityId] = $instance->getInvoiceTotals($invoiceObj, $discount);
$entityTotals[$entityId] = $instance->getInvoiceTotals($invoiceObj, $discount);
......
......@@ -84,12 +84,10 @@ class Billrun_Utils_Arrayquery_Aggregate {
}
protected function _project($expression, $data) {
$retData = array();
foreach ($expression as $key => $value) {
if($value) {
$retData[$key] = Billrun_Util::getIn($data, (is_numeric($value) ? $key : $this->clearLeadingDollar($value)) );
}
$retData = [];
$aggregateExpretion = new Billrun_Utils_Arrayquery_Aggregate_Expression();
foreach($data as $key => $dataValue) {
$retData[$key] = $aggregateExpretion->evaluate($dataValue, $expression ,$dataValue);
}
return $retData;
......
......@@ -65,6 +65,20 @@ class Billrun_Utils_Arrayquery_Aggregate_Expression {
return $ret;
}
/**
*
*/
protected static function clearLeadingDollar($fieldsWithDollar) {
if(!is_array($fieldsWithDollar)) {
return preg_replace('/^\$/', '', $fieldsWithDollar);
}
foreach($fieldsWithDollar as &$value) {
$value = preg_replace('/^\$/', '', $value);
}
return $fieldsWithDollar;
}
//======================================= instancing logic ==============================
protected function _first($data, $expression, $pastValue = FALSE) {
$result = $pastValue;
......@@ -83,8 +97,8 @@ class Billrun_Utils_Arrayquery_Aggregate_Expression {
return $result;
}
protected function _push($data, $expression, $pastValue) {
$result = empty($pastValue) ? array() : $pastValue;
protected function _push($data, $expression, $pastValue = FALSE) {
$result = empty($pastValue) ? array() : is_array($pastValue) ? $pastValue : [$pastValue];
if(is_array($expression)) {
foreach($expression as $dstKey => $subExpression) {
$addedData[$dstKey] = $this->evaluate($data, $subExpression);
......
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
var globalSetting = {
storageVersion: '0.0.1',
serverUrl: "",
serverApiVersion: '5.9.2',
serverApiVersion: '5.9',
serverApiTimeOut: 300000, // 5 minutes
serverApiDebug: false,
serverApiDebugQueryString: 'XDEBUG_SESSION_START=netbeans-xdebug',
......
<!DOCTYPE html> <html> <head> <title>BillRun Cloud</title> <meta charset=utf-8> <meta name=viewport content="width=device-width,initial-scale=1"> <link rel="shortcut icon" href=/favicon.ico> <link href="bundle.css?53757bed1a1c973f5869" rel="stylesheet"></head> <body> <div id=app> <div class=container> <div class="col-md-4 col-md-offset-4"> <div style=margin-top:33%;text-align:center> <img src=/img/billRun-cloud-logo.png height=50> <br/> <br/> <br/> <p>Loading...</p> </div> </div> </div> </div> <script src=/globalSettings.js></script> <script async src=/vendors/ckeditor/ckeditor.js></script> <script type="text/javascript" src="bundle.vendor.js?53757bed1a1c973f5869"></script><script type="text/javascript" src="bundle.app.js?53757bed1a1c973f5869"></script></body> </html>
\ No newline at end of file
<!DOCTYPE html> <html> <head> <title>BillRun Cloud</title> <meta charset=utf-8> <meta name=viewport content="width=device-width,initial-scale=1"> <link rel="shortcut icon" href=/favicon.ico> <link href="bundle.css?ee4bdf860b874ba9cd6b" rel="stylesheet"></head> <body> <div id=app> <div class=container> <div class="col-md-4 col-md-offset-4"> <div style=margin-top:33%;text-align:center> <img src=/img/billRun-cloud-logo.png height=50> <br/> <br/> <br/> <p>Loading...</p> </div> </div> </div> </div> <script src=/globalSettings.js></script> <script async src=/vendors/ckeditor/ckeditor.js></script> <script type="text/javascript" src="bundle.vendor.js?ee4bdf860b874ba9cd6b"></script><script type="text/javascript" src="bundle.app.js?ee4bdf860b874ba9cd6b"></script></body> </html>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment