Search code examples
phpyii2yii2-advanced-appbilling

Slab wise electricity bill calculation in Yii2


I am working on yii2. I want to calculate electricity bill slab wise. For this, I have already inserted slabs and their respective rates in a table. Now I want to calculate it.

My units are 128.82 and below is my slab and their rates.

enter image description here

I am able to carry out the slabs by using the following query

$mData = \Yii::$app->db->createCommand("SELECT s.`slab_start`, s.`slab_end`, s.`rate`, r.`cust_id` 
FROM `mdc_tariff_slabs` s 
INNER JOIN `mdc_meter_cust_rel` r ON s.`t_id` = r.`tariff_id`
WHERE r.`cust_id` = $consumer_no")->queryAll();

array(3) { [0]=> array(3) { ["slab_start"]=> string(1) "1" ["slab_end"]=> string(3) "100" ["rate"]=> string(2) "10" } [1]=> array(3) { ["slab_start"]=> string(3) "101" ["slab_end"]=> string(3) "150" ["rate"]=> string(2) "12" } [2]=> array(3) { ["slab_start"]=> string(3) "151" ["slab_end"]=> NULL ["rate"]=> string(2) "14" } }

Note: The slabs may differ in future

I have tried the following code, set the slabs according to my requirements and tried to get the bill

 public static function calcBill($units)
{

    if($units < 100){
        $bill = $units*10;
    }elseif($units > 100 && $units <=150){
        $temp = 100*10;
        $remaining_units = $units - 100;
        $bill = $temp + ($remaining_units *12);
    }else{
        $temp = (100*10)+ (100*12);
        $remaining_units = $units - 150;
        $bill = $temp + ($remaining_units *14);
    }

    echo $bill;
    die();
}

The bill is 1345.84.

Now what I am trying to do is to pass the slabs as well and not to hard code them. I am finding it a bit difficult.

Update 1

I have 3 slabs/units limit. Each slab has its own rate. Rate is used to calculate the bill. Below is the scenario.

  • if units are <= 100 then charge Rs 10 rupee/unit
  • if units are > 100 && <= 150 then charge Rs 12 rupee/unit
  • if units are > 150 then charge Rs 14 rupee/unit

In my array, I have all the three values except for the last one which has only slab_start and rate.

By hard coding the slabs and rate I can calculate the bill but I want to calculate while using the array.

So the code may be look likes

if($units < slab_end){
        $bill = $units*rate;
    }elseif($units > slab_start && $units <=slab_end){<=slab_end
        $temp = prev_slab_start *rate; 
        $remaining_units = $units - prev_slab;
        $bill = $temp + ($remaining_units *rate);
    }else{
        $temp = (prev_slab*10)+ (prev_slab*12);
        $remaining_units = $units - slab_start;
        $bill = $temp + ($remaining_units *14);
    }

I don't know the above logic is correct or not.

Note: Slab range and quantity may be changed. So I want to have a generic function that will do the needful

Update 2

Following the solution mentioned.

 $array = ArrayHelper::map($mData, 'slab_end', 'rate');

var_dump($array);
die();

The above gives me

array(3) { [100]=> string(2) "10" [150]=> string(2) "12" [""]=> string(2) "14" }

Then I have added a function

 public function billCalc($input, $slabs)
{
    $result = [];
    $bill = 0;
    $previous_slab = 0;

    foreach($slabs as $slab => $rate)
    {

        // calculate distance between current and previous slab
        $slab_distance = $slab - $previous_slab;
        // if current remainder of input value is >= distance, add distance to result,
        // and subtract distance from remainder of input
        if( $input >= $slab_distance )
        {
            $result[] = $slab_distance;
            $bill += $slab_distance * $rate;
            $input -= $slab_distance;
        }// otherwise, add remainder as last item of result, and break out of the loop here
        else
            {
            $result[] = $input;
            $bill += $input * $rate;
            break;
        }
        $previous_slab = $slab;
    }
    var_dump($result, $bill);
    exit();
}

Now I am facing an issue. When the input is less than 150 the result is perfectly ok, but when it's greater or equal to 150 I am getting below error

A non-numeric value encountered

It's giving me this error at

$slab_distance = $slab - $previous_slab;

Any help would be highly appreciated.


Solution

  • So you simply want to multiply the amount covered by each slab (“units”) with the rate then - so I would create an array that has the slab end as key, and the rate as value - then you can simply loop over that with the extended foreach syntax, and have access to the rate corresponding to the slab at all times.

    Expanding on my answer to the previous question, this would look like this then:

    $input = '128.82';
    $slabs = [100 => 10, 150 => 12, PHP_INT_MAX => 14];
    
    $result = [];
    $bill = 0;
    $previous_slab = 0;
    
    foreach($slabs as $slab => $rate) {
      // calculate distance between current and previous slab
      $slab_distance = $slab - $previous_slab;
      // if current remainder of input value is >= distance, add distance to result,
      // and subtract distance from remainder of input
      if( $input >= $slab_distance ) {
        $result[] = $slab_distance;
        $bill += $slab_distance * $rate;
        $input -= $slab_distance;
      }
      // otherwise, add remainder as last item of result, and break out of the loop here
      else {
        $result[] = $input;
        $bill += $input * $rate;
        break;
      }
      $previous_slab = $slab;
    }
    
    var_dump($result, $bill);
    

    Result:

    array (size=2)
      0 => int 100
      1 => float 28.82
    
    float 1345.84
    

    My approach again uses only the upper slab boundaries here - you don’t have one in your last record from the database, so I am substituting PHP_INT_MAX here for that NULL value. Taking that into account, when you create that array from your database content, should be not challenge, I hope.

    (If you only need the 1345.84 as result here, and not the array that shows the distribution accross the indivudual slabs, then you can just remove all lines that contain $result anywhere.)