Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

zaster's avatar

Bill Total - A better approach ?

There should be a better way to do this (Refer to the mount method)

       foreach ($bill->billItems as $billItem) {
           $this->billTotal += $billItem->qty * $billItem->rate;
       }

       foreach ($bill->billAccounts as $billAccount) {
           $this->billTotal += $billAccount->amount;
       }

The entire livewire class looks like this

<?php

namespace App\Http\Livewire;

use Carbon\Carbon;
use App\Models\Bill;
use App\Models\Comp;
use App\Models\Item;
use App\Models\Account;
use App\Models\Company;
use Livewire\Component;
use App\Models\BillItem;
use App\Models\Category;
use App\Models\BillAccount;
use Illuminate\Http\Request;
use Livewire\WithFileUploads;

class BillForm extends Component
{
    use WithFileUploads;

    public Bill $bill;
    public BillItem $billItem;
    public BillAccount $billAccount;

    public $users;
    public $companies;
    public $comps;

    public $categories;
    public $items;
    public $accounts;

    public $billItems;
    public $billAccounts;

    public $billTotal;
    public $billItemAmount;


    public $confirmingBillItemDeletion = false;
    public $confirmingBillAccountDeletion = false;

    public $selectedCompany = null;
    //public $selectedUser = null;
    public $selectedComp = 1;
    public $selectedCategory = null;
    public $selectedItem = null;
    public $selectedAccount = null;

    protected function rules()
    {
        return [
            'bill.company_id' => 'required',
            'bill.comp_id' => 'required',
            'bill.reference' => 'nullable',
            'bill.description' => 'nullable',
            'bill.date' => 'nullable',
            'billItem.bill_id' => 'nullable',
            'billItem.item_id' => 'nullable',
            'billItem.job_id' => 'nullable',
            'billItem.account_id' => 'nullable',
            'billItem.description' => 'nullable',
            'billItem.qty' => 'nullable',
            'billItem.rate' => 'nullable',
            'billItemAmount' => 'nullable',
            'billAccount.account_id' => 'nullable',
            'billAccount.description' => 'nullable',
            'billAccount.amount' => 'nullable',
        ];
    }

    //Customize validation
    protected $validationAttributes = [
        'selectedCompany' => 'provider',
        'selectedCategory' => 'category',
        'selectedItem' => 'item',
    ];

    public function updated($propertyName)
    {
        $this->validateOnly($propertyName);

    }


    public function updatedSelectedCategory()
    {
        $category_id = $this->selectedCategory;
        $category = Category::findOrFail($category_id);
        $this->items = $category->items;
        $this->selectedItem = $category->items->first()->id;
    }

    public function updatedBillItemQty()
    {
        $this->billItem->rate = $this->billItemAmount / $this->billItem->qty;

    }

    public function updatedBillItemRate()
    {
        $this->billItemAmount = $this->billItem->qty * $this->billItem->rate;
    }

    public function updatedBillItemAmount()
    {
        $this->billItem->rate = $this->billItemAmount / $this->billItem->qty;
    }

    // Delete BillItem Show Modal
    public function confirmBillItemDeletion($id)
    {
        $this->confirmingBillItemDeletion = $id;
    }

   // Delete BillItem
   public function deleteBillItem(BillItem $billItem)
   {
       $billItem->delete();
       $this->confirmingBillItemDeletion = false;
       return redirect(request()->header('Referer')); //To refresh the page
   }

    // Delete BillItem Show Modal
    public function confirmBillAccountDeletion($id)
    {
        $this->confirmingBillAccountDeletion = $id;
    }

   // Delete BillAccount
   public function deleteBillAccount(BillAccount $billAccount)
   {
       $billAccount->delete();
       $this->confirmingBillAccountDeletion = false;
       return redirect(request()->header('Referer')); //To refresh the page
   }


   public function mount(Bill $bill, BillItem $billItem, BillAccount $billAccount)
   {
        $this->bill = $bill ?? new Bill();
        $this->billItem = $billItem ?? new BillItem();
        $this->billAccount = $billAccount ?? new BillAccount();

        $this->bill->date = Carbon::now()->format('Y-m-d');
        $this->bill->comp_id = 1;
        $this->companies = Company::all()->sortBy('name');
        $this->comps = Comp::all();
        $this->categories = Category::all();
        $this->items = Item::all();
        $this->accounts = Account::all();

       if($bill->billItems->count() > 0 )
       {
           $this->billItems = $bill->billItems; //To display existing billItems
       }

       if($bill->billAccounts->count() > 0 )
       {
           $this->billAccounts = $bill->billAccounts;
       }

       foreach ($bill->billItems as $billItem) {
           $this->billTotal += $billItem->qty * $billItem->rate;
       }

       foreach ($bill->billAccounts as $billAccount) {
           $this->billTotal += $billAccount->amount;
       }

   }

    public function save($action)
    {
        //$this->validate();

        // Bill is in create mode
        if($this->bill->id == null)
        {
            $this->validate([
                'bill.company_id' => 'required',
                'bill.comp_id' => 'required',
                'bill.reference' => 'nullable',
                'bill.date' => 'nullable',
                'bill.description' => 'nullable',
            ]);


            Bill::create([
                'company_id' => $this->bill->company_id,
                'comp_id' => $this->bill->comp_id,
                'date' => $this->bill->date,
                'reference' => $this->bill->reference,
                'description' => $this->bill->description,
                'entered_by' => null,
                'verified_by' => null,
                'paid_by' => null,
            ]);

            $lastBillId = Bill::latest()->first()->id;

            return redirect()->route('employees.pays.bills.edit', $lastBillId);
        }
        // Bill is in edit mode
        else
        {
            if($action == "addBillItem")
            {

                //Bill::latest()->first()->id;
                $this->billItem->fill([
                    'bill_id' => $this->bill->id,
                    'item_id' => $this->selectedItem,
                    'job_id' => $this->billItem->job_id,
                    'account_id' => $this->billItem->account_id,
                    'description' => $this->billItem->description,
                    'qty' => $this->billItem->qty,
                    'rate' => $this->billItem->rate,
                    'paid' => null,
                    'verified' => null,
                ]);

                $this->billItem->save();
            }

            elseif($action == "addBillAccount")
            {
                $this->billAccount->fill([
                    'bill_id' => $this->bill->id,
                    'account_id' => $this->billAccount->account_id,
                    'description' => $this->billAccount->description,
                    'paid' => 1,
                    'verify' => 1,
                    'amount' => $this->billAccount->amount,
                ]);

                $this->billAccount->save();
            }

            elseif($action == "addBill")
            {

                $this->bill->fill([
                    'company_id' => $this->bill->company_id,
                    'comp_id' => $this->bill->comp_id,
                    'date' => $this->bill->date,
                    'reference' => $this->bill->reference,
                    'description' => $this->bill->description,
                    'entered_by' => null,
                    'verified_by' => null,
                    'paid_by' => null,
                ]);

                $this->bill->save();
            }
            return redirect()->route('employees.pays.bills.edit', $this->bill->id);
        }


    }

    public function render()
    {
        return view('livewire.bill-form');
    }
}

My Preference is to insert each and every record (BillItem and BillAccount) to the database (E.g. - 20 BillItems required 20 Submits per Bill)

However, i prefer to calculate $this->billTotal in a much optimized way

class Bill extends Model
{
..
..

    public function billItems()
    {
        return $this->hasMany(BillItem::class);
    }

    public function billAccounts()
    {
        return $this->hasMany(BillAccount::class);
    }


}
0 likes
3 replies
rodrigo.pedra's avatar
Level 56

As they are results of relations there is a shorter way using the sum method from collections:

$this->billTotal += $bill->billItems->sum(fn($billItem) => $billItem->qty * $billItem->rate)
    + $bill->billAccounts->sum('amount')

https://laravel.com/docs/9.x/collections#method-sum

But I would also save the results to a database column, some redundancy on these cases makes reporting and other statistics easier to calculate.

zaster's avatar

@rodrigo.pedra

Thank you. That worked.

Are you suggesting to have the total column saved in the database ?

I like that approach but then, we have to always make sure that total = qty * rate right ?

1 like
rodrigo.pedra's avatar

@zaster Yes, there is a janitor work to be sure it is always up to date.

But as it is a bill system, I imagine the bill does not change so often. And the benefit of having the total saved into the database, in my opinion outweighs the work needed to maintain it in sync.

1 like

Please or to participate in this conversation.