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

zaster's avatar

Livewire - Any thoughts on improving this component

There are 3 Models

One Bil will have many BillItems (One to Many Relationship) One Bill will have many BillAccounts (One to Many Relationship)

This is my livewire BillForm Component. This works well , but i feel that this can be improved alot(Especially the validation section)

I need your thoughts on this because i am going to code the rest of my project based on this component (there will be similar livewire componenets like this and my mistakes will be repeated over and over)

BillForm.php

<?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 Livewire\WithFileUploads;
use Illuminate\Support\Facades\Auth;

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 $confirmingBillItemDeletion = false;
    public $confirmingBillAccountDeletion = false;

    public $selectedCompany = null;
    public $selectedCategory = null;
    public $selectedItem = null;
    public $selectedAccount = null;

    public $verifiedButton = false;

    protected function rules()
    {
        return [
            'bill.company_id' => 'required',
            'bill.comp_id' => 'required',
            'bill.reference' => 'nullable',
            'bill.description' => 'nullable',
            'bill.date' => 'nullable',
            'bill.total' => 'nullable', //Bill Section Ends
            'selectedCategory' => 'nullable',
            'selectedItem' => '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',
            'billItem.amount' => 'nullable', //BillItem Section Ends
            'billAccount.account_id' => 'nullable',
            'billAccount.description' => 'nullable',
            'billAccount.amount' => 'nullable', //BillAccount Section Ends
        ];
    }

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

    //Realtime validation
    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->validate([
            'billItem.qty' => 'required|numeric'
        ]);//For realtime validation
        $this->billItem->rate = $this->billItem->amount / $this->billItem->qty;

    }

    public function updatedBillItemRate()
    {
        $this->validate([
            'billItem.rate' => 'required|numeric'
        ]);//For realtime validation
        $this->billItem->amount = $this->billItem->qty * $this->billItem->rate;
    }

    public function updatedBillItemAmount()
    {
        $this->validate([
            'billItem.amount' => 'required|numeric'
        ]);//For realtime validation

        $this->billItem->rate = $this->billItem->amount / $this->billItem->qty;
    }

    public function updatedBillAccountAmount()
    {
        //dd("test");
        $this->validate([
            'billAccount.amount' => 'required|numeric'
        ]);//For realtime validation
    }

    // 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 changeStatus($value)
   {
        $this->validate();

        $this->bill->fill([
                'status' => $value,
            ]);

        $this->bill->save();

   }


   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->companies = Company::all()->sortBy('name');
        $this->comps = Comp::all();
        $this->categories = Category::all();
        $this->items = Item::all();

        $this->accounts = Account::orderby('number', 'asc')
        ->where('id', 15)->orWhere('type_id', 2)->get();

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

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

        $this->bill->total += $bill->billItems->sum(fn($billItem) => $billItem->qty * $billItem->rate)
        + $bill->billAccounts->sum('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.total' => '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,
                'total' => $this->bill->total,
                'status' => $this->bill->status,
                'entered_by' => Auth::user()->id,
                '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")
            {

                $this->validate([
                    'selectedCategory' => 'required',
                    'selectedItem' => 'required',
                    'billItem.bill_id' => 'nullable',
                    'billItem.item_id' => 'nullable',
                    'billItem.job_id' => 'nullable',
                    'billItem.account_id' => 'nullable',
                    'billItem.description' => 'nullable',
                    'billItem.qty' => 'required',
                    'billItem.rate' => 'required',
                    'billItem.amount' => 'required',
                ]);
                //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->validate([
                    'billAccount.account_id' => 'required',
                    'billAccount.description' => 'nullable',
                    'billAccount.amount' => 'required',
                ]);

                $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")
            {
                //dd($this->bill);
                $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,
                    'status' => $this->bill->status,
                    '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');
    }
}


bill-form-blade.php

<div>
    <form method="post" wire:submit.prevent="save" action="" class="xl:h-screen">
    @csrf

        {{-- Row 1 --}}
        <div class="grid grid-cols-1 p-4 xl:grid-cols-12 gap-x-4 gap-y-4">
            {{-- Provider (Mainly includes the company list and Self Companies like Mehran)--}}
            <div class="xl:col-span-3">
                <b class="uppercase">Provider</b>
                <select wire:model="bill.company_id"
                class="block w-full mt-1 border-gray-300 rounded-md shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
                        <option value="" selected>Choose a Provider</option>
                        @foreach($companies as $company)
                        <option value="{{ $company->id }}">{{ $company->name }}</option>
                        @endforeach
                </select>
                <x-jet-input-error for="bill.company_id" class="mt-2" />
            </div>

            {{-- Comp --}}
            <div class="xl:col-span-1">
                <b class="uppercase">Comp</b>
                <select wire:model="bill.comp_id"
                class="block w-full mt-1 border-gray-300 rounded-md shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
                        <option value="">Select</option>
                        @foreach($comps as $comp)
                        <option value="{{ $comp->id }}">{{ $comp->short_name }}</option>
                        @endforeach
                </select>
                <x-jet-input-error for="bill.comp_id" class="mt-2" />
            </div>

            {{-- reference --}}
            <div class="xl:col-span-2">
                <b class="uppercase">reference</b>
                <x-jet-input wire:model.lazy="bill.reference" id="reference" type="text" class="block w-full mt-1"  />
                <x-jet-input-error for="bill.reference" class="mt-2" />
            </div>

            {{-- date --}}
            <div class="xl:col-span-2">
                <b class="uppercase">date</b>
                <x-jet-input wire:model.lazy="bill.date" id="date" type="date" class="block w-full mt-1"  />
                <x-jet-input-error for="bill.date" class="mt-2" />
            </div>

            {{-- description --}}
            <div class="xl:col-span-4">
                <b class="uppercase">description</b>
                <x-jet-input wire:model.lazy="bill.description" id="description" type="text" class="block w-full mt-1"  />
                <x-jet-input-error for="bill.description" class="mt-2" />
            </div>

        </div>
{{-- BillItems and BillAccounts will be visible only when there is an existing bill         --}}

        {{-- Row 1.1 --}}
        <div class="grid grid-cols-1 p-4 xl:grid-cols-12 gap-x-4 gap-y-4">
            <div class="xl:col-span-2">
                <x-success-button class="w-full h-full" wire:click="save('addBill')">
                    {{ $bill->id ? 'Update' : 'Save' }}
                </x-success-button>
            </div>
            <div class="xl:col-span-2">
                @if($bill->id != NULL && $bill->status == "Pending")
                    <x-success-button class="w-full h-full" wire:click="changeStatus('Entered')">
                        {{ 'Enter' }}
                    </x-success-button>
                @endif
                @if($bill->status == "Entered")
                    <x-success-button class="w-full h-full" wire:click="changeStatus('Verified')">
                        {{ 'Verify' }}
                    </x-success-button>
                @endif
            </div>
            <div class="xl:col-span-2">
                {{-- <x-jet-label for="status" value="{{ __('STATUS') }}" class="font-bold text-left" />
                <select wire:model="bill.status"
                    class="block w-full mt-1 border-gray-300 rounded-md shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
                        <option value="Pending">Pending</option>
                        <option value="Entered">Entered</option>
                        <option value="Verified">Verified</option>
                        <option value="Error">Error</option>
                </select> --}}
            </div>


            <div class="xl:col-span-2">
                    <x-jet-danger-button class="w-full h-full" wire:click="changeStatus('Error')">
                        {{ 'Error' }}
                    </x-jet-danger-button>
            </div>
            <div class="xl:col-span-4">
                <b class="uppercase">bill total</b>
                <x-jet-input wire:model.lazy="bill.total" id="billTotal" type="text" class="block w-full mt-1" readonly  />
            </div>
        </div>

@if($bill->id != null)
        {{-- Row 2 --}}
        <div class="grid grid-cols-1 px-4 mt-4 xl:grid-cols-12">
        {{-- bill items --}}
            <div class="xl:col-span-12">
                <b class="w-full underline uppercase">bill items</b>
            </div>
        </div>

        {{-- Row 3 --}}
        <div class="grid grid-cols-1 p-4 overflow-auto xl:grid-cols-12 gap-x-4 gap-y-4">
            {{-- <table class="min-w-full text-center divide-y divide-gray-200"> --}}
            <table class="col-span-12 overflow-x-visible table-auto">
                <thead class="w-full font-bold text-gray-700 bg-gray-300">
                    <th scope="col" class="px-6 py-3 text-xs tracking-wider uppercase">
                        <div class="xl:col-span-2">
                            <b class="uppercase">category</b>
                        </div>
                    </th>
                    <th scope="col" class="px-6 py-3 text-xs tracking-wider uppercase">
                        <div class="xl:col-span-2">
                            <b class="uppercase">item</b>
                        </div>
                    </th>
                    <th scope="col" class="px-6 py-3 text-xs tracking-wider uppercase">
                        <div class="xl:col-span-1">
                            <b class="uppercase">job no</b>
                        </div>
                    </th>
                    <th scope="col" class="px-6 py-3 text-xs tracking-wider uppercase">
                        <div class="xl:col-span-2">
                            <b class="uppercase">description</b>
                        </div>
                    </th>
                    <th scope="col" class="px-6 py-3 text-xs tracking-wider uppercase">
                        <div class="xl:col-span-1">
                            <b class="uppercase">qty</b>
                        </div>
                    </th>
                    <th scope="col" class="px-6 py-3 text-xs tracking-wider uppercase">
                        <div class="xl:col-span-1">
                            <b class="uppercase">rate</b>
                        </div>
                    </th>
                    <th scope="col" class="px-6 py-3 text-xs tracking-wider uppercase">
                        <div class="xl:col-span-2">
                            <b class="uppercase">amount</b>
                        </div>
                    </th>
                    <th scope="col" class="px-6 py-3 text-xs tracking-wider uppercase">
                        <div class="xl:col-span-1">

                        </div>
                    </th>
                </thead>

                <tbody class="text-left bg-white divide-y divide-gray-200">
                    @if($billItems)
                    @foreach ($billItems as $billItem)
                        <tr>
                            <td class="px-6 py-2 text-sm text-center text-gray-500 border-2 border-gray-200 whitespace-nowrap">
                                {{ $billItem->item->category->name }}
                            </td>
                            <td class="px-6 py-2 text-sm text-center text-gray-500 border-2 border-gray-200 whitespace-nowrap">
                                {{ $billItem->item->name }}</b>
                            </td>
                            <td class="px-6 py-2 text-sm text-center text-gray-500 border-2 border-gray-200 whitespace-nowrap">
                                {{ $billItem->job_no }}
                            </td>
                            <td class="px-6 py-2 text-sm text-center text-gray-500 border-2 border-gray-200 whitespace-nowrap">
                                {{ $billItem->description }}
                            </td>
                            <td class="px-6 py-2 text-sm text-center text-gray-500 border-2 border-gray-200 whitespace-nowrap">
                                {{ $billItem->qty }}
                            </td>
                            <td class="px-6 py-2 text-sm text-center text-gray-500 border-2 border-gray-200 whitespace-nowrap">
                                {{ $billItem->rate }}
                            </td>
                            <td class="px-6 py-2 text-sm text-center text-gray-500 border-2 border-gray-200 whitespace-nowrap">
                                {{ $billItem->qty * $billItem->rate }}
                            </td>
                             <td class="px-6 py-2 text-sm text-center text-gray-500 border-2 border-gray-200 whitespace-nowrap">
                                <a href="{{ route('employees.pays.billitems.edit', $billItem) }}"><x-warning-button>Edit</x-warning-button></a>
                                {{-- <a href="{{ route('employees.pay.bills.edit', $user) }}"><x-warning-button>Edit</x-warning-button></a> --}}
                                <x-jet-danger-button wire:click="confirmBillItemDeletion({{ $billItem->id }})">Delete</x-jet-danger-button>
                            </td>
                        </tr>
                     @endforeach
                     @endif
                </tbody>
            </table>
        </div>


        {{-- Row 3.1 --}}
        <div class="grid grid-cols-1 p-4 xl:grid-cols-12 gap-x-4 gap-y-4">
            <div class="xl:col-span-2">
                <label for="category" class="font-bold uppercase">category</label>
                <select wire:model="selectedCategory"
                    class="block w-full mt-1 border-gray-300 rounded-md shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
                            <option value="" selected>Choose a Category</option>
                            @foreach($categories as $category)
                            <option value="{{ $category->id }}">{{ $category->name }}</option>
                            @endforeach
                    </select>
                <x-jet-input-error for="selectedCategory" class="mt-2" />
            </div>



            <div class="xl:col-span-2">
            <label for="item" class="font-bold uppercase">item</label>
                @if($selectedItem == null)
                <div class="col-span-12 xl:col-span-6">
                    <select wire:model="selectedItem" class="block w-full mt-1 border-gray-300 rounded-md shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
                            <option value="" selected>Choose an Item</option>
                    </select>
                    <x-jet-input-error for="selectedItem" class="mt-2" />
                </div>
                @else

                <div class="col-span-12 xl:col-span-6">
                    <select wire:model="selectedItem" class="block w-full mt-1 border-gray-300 rounded-md shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
                            @foreach($items as $item)
                                <option value="{{ $item->id }}">{{ $item->name }}</option>
                            @endforeach
                    </select>
                    <x-jet-input-error for="selectedItem" class="mt-2" />
                </div>
                @endif

            </div>



            <div class="xl:col-span-1">
                <label for="job no" class="font-bold uppercase">job no</label>
                <select wire:model.lazy="billItem.job_id"
                    class="block w-full mt-1 border-gray-300 rounded-md shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
                </select>
                <x-jet-input-error for="billItem.job_id" class="mt-2" />
            </div>
            <div class="xl:col-span-2">
                <label for="description" class="font-bold uppercase">description</label>
                <x-jet-input wire:model.lazy="billItem.description" id="description" type="text" class="block w-full mt-1"  />
                <x-jet-input-error for="billItem.description" class="mt-2" />
            </div>
            <div class="xl:col-span-1">
                <label for="qty" class="font-bold uppercase">qty</label>
                <x-jet-input wire:model.lazy="billItem.qty" id="qty" type="text" class="block w-full mt-1"  />
                <x-jet-input-error for="billItem.qty" class="mt-2" />
            </div>
            <div class="xl:col-span-1">
                <label for="rate" class="font-bold uppercase">rate</label>
                <x-jet-input wire:model.lazy="billItem.rate" id="rate" type="text" class="block w-full mt-1"  />
                <x-jet-input-error for="billItem.rate" class="mt-2" />
            </div>
            <div class="xl:col-span-2">
                <label for="billItemAmount" class="font-bold uppercase">amount</label>
                <x-jet-input wire:model.lazy="billItem.amount" id="billItem.amount" type="text" class="block w-full mt-1"  />
                <x-jet-input-error for="billItem.amount" class="mt-2" />

            </div>
            <div class="xl:col-span-1">
                <x-success-button class="w-full h-full" wire:click="save('addBillItem')">
                    {{ 'add bill item' }}
                </x-success-button>
            </div>
        </div>



        {{-- Row 4 --}}
        <div class="grid grid-cols-1 px-4 mt-4 xl:grid-cols-12">
        {{-- bill accounts --}}
            <div class="col-span-12">
                <b class="underline uppercase">bill accounts</b>
            </div>
        </div>

        {{-- Row 5 --}}
        <div class="grid grid-cols-1 px-4 mt-4 xl:grid-cols-12">
            <table class="col-span-12 overflow-x-visible table-auto">
                <thead class="w-full font-bold text-gray-700 bg-gray-300">
                    <th scope="col" class="px-6 py-3 text-xs tracking-wider uppercase">
                        <div class="xl:col-span-2">
                            <b class="uppercase">account</b>
                        </div>
                    </th>
                    <th scope="col" class="px-6 py-3 text-xs tracking-wider uppercase">
                        <div class="xl:col-span-7">
                            <b class="uppercase">description</b>
                        </div>
                    </th>
                    <th scope="col" class="px-6 py-3 text-xs tracking-wider uppercase">
                        <div class="xl:col-span-2">
                            <b class="uppercase">amount</b>
                        </div>
                    </th>
                    <th scope="col" class="px-6 py-3 text-xs tracking-wider uppercase">
                        <div class="xl:col-span-1">

                        </div>
                    </th>
                </thead>

                <tbody class="text-left bg-white divide-y divide-gray-200">
                    @if($billAccounts)
                    @foreach ($billAccounts as $billAccount)
                        <tr>
                            <td class="px-6 py-2 text-sm text-center text-gray-500 border-2 border-gray-200 whitespace-nowrap">
                                {{ $billAccount->account->name }}
                            </td>
                            <td class="px-6 py-2 text-sm text-center text-gray-500 border-2 border-gray-200 whitespace-nowrap">
                                {{ $billAccount->description }}</b>
                            </td>
                            <td class="px-6 py-2 text-sm text-center text-gray-500 border-2 border-gray-200 whitespace-nowrap">
                                {{ $billAccount->amount }}
                            </td>
                            <td class="px-6 py-2 text-sm text-center text-gray-500 border-2 border-gray-200 whitespace-nowrap">
                                <a href="{{ route('employees.pays.billaccounts.edit', $billAccount) }}"><x-warning-button>Edit</x-warning-button></a>
                                <x-jet-danger-button wire:click="confirmBillAccountDeletion({{ $billAccount->id }})">Delete</x-jet-danger-button>
                            </td>
                        </tr>
                    @endforeach
                    @endif
                </tbody>
            </table>
        </div>


        {{-- Row 5.1 --}}
        <div class="grid grid-cols-1 p-4 xl:grid-cols-12 gap-x-4 gap-y-4">
            <div class="xl:col-span-2">
                <label for="account" class="font-bold uppercase">account</label>
                <select wire:model="billAccount.account_id"
                    class="block w-full mt-1 border-gray-300 rounded-md shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
                            <option value="" selected>Choose an Account</option>
                            @foreach($accounts as $account)
                            <option value="{{ $account->id }}">{{ $account->name }}</option>
                            @endforeach
                    </select>
                <x-jet-input-error for="billAccount.account_id" class="mt-2" />
            </div>

            <div class="xl:col-span-7">
                <b class="uppercase">description</b>
                <x-jet-input wire:model.lazy="billAccount.description" id="accountDescription" type="text" class="block w-full mt-1"  />
                <x-jet-input-error for="billAccount.description" class="mt-2" />
            </div>
            <div class="xl:col-span-2">
                <b class="uppercase">amount</b>
                 <x-jet-input wire:model.lazy="billAccount.amount" id="accountAmount" type="text" class="block w-full mt-1"  />
                <x-jet-input-error for="billAccount.amount" class="mt-2" />
            </div>
            <div class="xl:col-span-1">
                <x-success-button class="w-full h-full" wire:click="save('addBillAccount')">
                    {{ 'add bill account' }}
                </x-success-button>
            </div>

        </div>
    @endif

    </form>



    <div>

        <div class="grid grid-cols-1 p-4 bg-green-200 border-2 border-black xl:grid-cols-12 gap-x-4 gap-y-4 mt-20">
            <div class="xl:col-span-12">
                <b class="underline uppercase">Insructions</b>
            </div>

            <div class="xl:col-span-3">
                <b>Bill Section</b>
                <ul class="px-4 list-disc">
                <li>PK bills and PKL bills should be taken separately. E.g.- PK Vmas Bill, PKL Vmas Bill</li>
                <!-- ... -->
                </ul>
            </div>
            <div class="xl:col-span-3">
                <b>Another Section</b>

            </div>
        </div>


        <!-- Delete BillItem Confirmation Modal -->
        <x-jet-dialog-modal wire:model="confirmingBillItemDeletion">
            <x-slot name="title">
                {{ __('Delete BillItem') }}
            </x-slot>

            <x-slot name="content">
                {{ __('Are you sure you want to delete this BillItem?') }}
            </x-slot>

            <x-slot name="footer">
                <x-jet-secondary-button wire:click="$set('confirmingBillItemDeletion', false)" wire:loading.attr="disabled">
                    {{ __('Cancel') }}
                </x-jet-secondary-button>

                <x-jet-danger-button class="ml-2" wire:click="deleteBillItem({{ $confirmingBillItemDeletion }})" wire:loading.attr="disabled">
                    {{ __('Delete') }}
                </x-jet-danger-button>
            </x-slot>
        </x-jet-dialog-modal>


        <!-- Delete BillAccount Confirmation Modal -->
        <x-jet-dialog-modal wire:model="confirmingBillAccountDeletion">
            <x-slot name="title">
                {{ __('Delete BillAccount') }}
            </x-slot>

            <x-slot name="content">
                {{ __('Are you sure you want to delete this BillAccount?') }}
            </x-slot>

            <x-slot name="footer">
                <x-jet-secondary-button wire:click="$set('confirmingBillAccountDeletion', false)" wire:loading.attr="disabled">
                    {{ __('Cancel') }}
                </x-jet-secondary-button>

                <x-jet-danger-button class="ml-2" wire:click="deleteBillAccount({{ $confirmingBillAccountDeletion }})" wire:loading.attr="disabled">
                    {{ __('Delete') }}
                </x-jet-danger-button>
            </x-slot>
        </x-jet-dialog-modal>


    </div>
</div>

Bill.php

<?php

namespace App\Models;

use App\Models\Comp;
use App\Models\Company;
use App\Models\BillItem;
use App\Models\BillAccount;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Bill extends Model
{
    use SoftDeletes;
    use HasFactory;

    protected $guarded = [];

    public function getStatusAttribute($value)
    {
        if ($value == 0) { return "Pending"; }
        if ($value == 1) { return "Entered"; }
        if ($value == 2) { return "Verified"; }
        if ($value == 3) { return "Paid"; }
        if ($value == 4) { return "Error"; }
    }

    public function setStatusAttribute($value)
    {
        //dd($value);
        if($value == "Pending") { $this->attributes['status'] = 0; }
        if($value == "Entered") { $this->attributes['status'] = 1; }
        if($value == "Verified") { $this->attributes['status'] = 2; }
        if($value == "Paid") { $this->attributes['status'] = 3; }
        if($value == "Error") { $this->attributes['status'] = 4; }

    }

    public function company()
    {
        return $this->belongsTo(Company::class);
    }

    public function comp()
    {
        return $this->belongsTo(Comp::class);
    }

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

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

    public function payBills()
    {
        return $this->belongsToMany('App\Models\Employee\PayBill', 'bill_paybill')->withPivot('id', 'bill_id', 'paybill_id')->withTimestamps();
    }


}

BillItem.php

<?php

namespace App\Models;

use App\Models\Item;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class BillItem extends Model
{
    use HasFactory;
    use SoftDeletes;

    protected $guarded = [];

    // protected $appends = ['qty'];


    public function bill()
    {
        return $this->belongsTo(Bill::class);
    }

    public function item()
    {
        return $this->belongsTo(Item::class);
    }

    public function getBillItemRateAttribute($rate)
    {
        return $rate / 10000;
    }

    public function setBillItemRateAttribute($value)
    {
        $this->attributes['rate'] = $value * 10000;
    }

}

BillAccount.php

<?php

namespace App\Models;

use App\Models\Account;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class BillAccount extends Model
{
    use HasFactory;
    use SoftDeletes;

    protected $guarded = [];

    public function bill()
    {
        return $this->belongsTo(Bill::class);
    }

    public function account()
    {
        return $this->belongsTo(Account::class);
    }


    public function getBillAmountAttribute($amount)
    {
        return $amount / 10000;
    }

    public function setBillAmountAttribute($value)
    {
        $this->attributes['amount'] = $value * 10000;
    }
}
0 likes
3 replies
webrobert's avatar
Level 51

Here is a first pass on your livewire component. (just the methods I edited or added)...

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

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

        $this->accounts     = Account::query()
            ->where('id', 15)->orWhere('type_id', 2) // <-- does this work properly? you may want to wrap them
            ->orderby('number', 'asc')
            ->get();

        $this->billAccounts = $bill->billAccounts;
        $this->billItems    = $bill->billItems;

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

    }

    public function save($action)
    {
       if (! $this->bill->id) $this->create();
       
	   $this->{$action}();

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

    public function addBillItem()
    {
        $this->validate([
            'selectedCategory' => 'required',
            'selectedItem' => 'required',
            'billItem.bill_id' => 'nullable',
            'billItem.item_id' => 'nullable',
            'billItem.job_id' => 'nullable',
            'billItem.account_id' => 'nullable',
            'billItem.description' => 'nullable',
            'billItem.qty' => 'required',
            'billItem.rate' => 'required',
            'billItem.amount' => 'required',
        ]);

        $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();
    }

    public function addBillAccount()
    {
        $this->validate([
            'billAccount.account_id' => 'required',
            'billAccount.description' => 'nullable',
            'billAccount.amount' => 'required',
        ]);

        $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();
    }
    public function addBill()
    {
        // validate here

        $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,
            'status' => $this->bill->status,
            'entered_by' => null,
            'verified_by' => null,
            'paid_by' => null,
        ]);

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

    private function create()
    {
        $this->validate([
            'bill.company_id' => 'required',
            'bill.comp_id' => 'required',
            'bill.reference' => 'nullable',
            'bill.date' => 'nullable',
            'bill.description' => 'nullable',
            'bill.total' => 'nullable',
        ]);

        $bill = 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,
            'total' => $this->bill->total,
            'status' => $this->bill->status,
            'entered_by' => Auth::user()->id,
            'verified_by' => null,
            'paid_by' => null,
        ]);

        return redirect()->route('employees.pays.bills.edit', $bill->id);
    }
zaster's avatar

@webrobert

       $this->accounts     = Account::query()
            ->where('id', 15)->orWhere('type_id', 2) // <-- does this work properly? you may want to wrap them
            ->orderby('number', 'asc')
            ->get();

This works.

Thank you for taking time to go through this.

1 like
webrobert's avatar

@zaster, happy to help.

and

public function addBillAccount()
{
    $this->validate([
        'billAccount.account_id' => 'required',
        'billAccount.description' => 'nullable',
        'billAccount.amount' => 'required',
    ]);

    $this->billAccount->fill([
        'bill_id' => $this->bill->id,
        'account_id' => $this->billAccount->account_id, // aren't these already filled??
        'description' => $this->billAccount->description, // aren't these already filled??
        'paid' => 1,
        'verify' => 1,
        'amount' => $this->billAccount->amount, // aren't these already filled??
    ]);

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

so just do this ....

public function addBillAccount()
{
    $this->validate([
        'billAccount.account_id' => 'required',
        'billAccount.description' => 'nullable',
        'billAccount.amount' => 'required',
    ]);

    $this->billAccount->fill([
        'bill_id' => $this->bill->id,
		// and these could probably be defaulted elsewhere...
        'paid' => 1, 
        'verify' => 1, 
    ]);

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

Please or to participate in this conversation.