How to handle missing records of a Product's attribute value in laravel 7 - laravel

Background Information:
I'm learning laravel 7 by creating an inventory system.
I have several models: Product, Stock, and Attribute.
The Product is like a group that holds all of the stock. Because I can have a Product like a speaker. But I can have many quantities of that same product that have different attribute values. This is when the Stock becomes important.
The Stock is where I have all of the items in my inventory.
For example: If I have 20 speakers, there will be one record in the products table and 20 records in the stocks table.
The Attribute is because I want to track different attributes depending on the Product. But the value of those attributes doesn't belong to the product; It belongs to the Stock of the product.
This is how my models relate:
A Product hasMany Stock
A Stock belongsTo a Product
A Product hasMany Attribute
An Attribute belongsTo a Product
An Attribute belongsToMany Stock
A Stock belongsToMany Attribute (I'm not very confident about this one.)
Goal:
The Product is L-Acoustics LA12X
The product's Attributes are Serial Number and Firmware
The product's Stock is the card that says "Stock Level".
For testing and simplicity, I copied the same values of Serial Number and Firmware to all of the Product's Stock; but in real life they should all have different serial numbers and firmware versions.
Problem:
I only get the output from the picture above when there is a value for each attribute. For example: if I delete the attribute value of the Serial Number from the first stock I get this.
Do you see how it shifted the Firmware value to the Serial Numbers column?
In the case that the record is deleted or the value hasn't been created I would like to put an empty cell in that table so that it looks like this. (I created this image by editing the HTML of the page, I still don't know how to get this output)
What I have tried:
I quickly thought of creating empty cells in my database belonging to the attribute so that when I loop through my records I get a perfect table. But this will quickly become a problem when my database increases size.
You could think of why would there be records that don't exist? Well one example is that my inventory is so old that there are some Serial Numbers that I can't retrieve. Therefore, there will be no records in the table.
Showing some code:
This is my database design:
This is my Product Model:
class Product extends Model {
protected $guarded = [];
protected $casts = [
'brand_id' => 'integer',
'category_id' => 'integer',
'price' => 'decimal:2',
'rental_price' => 'decimal:2',
'active' => 'boolean'
];
public function setNameAttribute($value)
{
$this->attributes['name'] = $value;
$this->attributes['slug'] = Str::slug($value);
}
public function brand()
{
return $this->belongsTo(Brand::class);
}
public function category()
{
return $this->belongsTo(Category::class);
}
public function stocks()
{
return $this->hasMany(Stock::class)->orderBy('barcode', 'asc');
}
public function attributes()
{
return $this->hasMany(Attribute::class);
}
}
This is my Stock Model:
class Stock extends Model {
protected $guarded = [];
protected $casts = [
'quantity' => 'integer',
'product_id' => 'integer',
];
public function product()
{
return $this->belongsTo(Product::class);
}
public function attributes()
{
return $this->belongsToMany(Attribute::class)->withPivot('value');
}
}
This is my Attribute Model:
class Attribute extends Model {
protected $guarded = [];
protected $casts = ['product_id' => 'integer'];
public function product()
{
return $this->belongsTo(Product::class);
}
public function stocks()
{
return $this->belongsToMany(Stock::class);
}
public function values()
{
// This method is empty because I don't know how to relate the attribute to its values.
}
I know I have dumped a lot of information but I really need help. I have an open mind and I'm open to new implementations.
UPDATE
Here is the code of my view:
<div class="block-content block-content-full">
<div class="table-responsive">
<table class="table table-borderless table-striped table-vcenter font-size-sm">
<thead>
<tr>
<th class="text-left">Barcode</th>
#foreach($product->attributes as $attribute)
<th class="text-center">{{ $attribute->name }}</th>
#endforeach
<th class="text-right">Condition</th>
<th class="text-right">Updated</th>
</tr>
</thead>
<tbody>
#forelse($product->stocks as $stock)
<tr>
<td class="text-left">
<strong>{{ $stock->barcode }}</strong>
</td>
#foreach($stock->attributes as $attribute)
<td class="text-center">{{ $attribute->pivot->value }}</td>
#endforeach
<td class="text-right">
#switch($stock->condition)
#case('Great')
<span class="badge badge-success"><i class="fa fa-check-circle mr-1"></i>Great</span>
#break
#case('Damaged')
<span class="badge badge-warning"><i class="fa fa-exclamation-circle mr-1"></i>Damage</span>
#break
#case('Broken')
<span class="badge badge-danger"><i class="fa fa-times-circle mr-1"></i>Broken</span>
#break
#endswitch
</td>
<td class="text-right">{{ $stock->updated_at->format('M d, Y') }}</td>
</tr>
#empty
<tr><td colspan="100" class="text-center">No Stock Available</td></tr>
#endforelse
</tbody>
</table>
</div>
</div>
Here is the show method in my controller:
public function show(Product $product)
{
return view('products.show', compact('product'));
}
UPDATE 2:
I updated my Attribute model to this:
class Attribute extends Model {
protected $guarded = [];
protected $casts = ['product_id' => 'integer'];
public function getValueByBarcode($stock)
{
return $this->values()->where('barcode', $stock)->first()->value ?? '';
}
public function product()
{
return $this->belongsTo(Product::class);
}
public function stocks()
{
return $this->belongsToMany(Stock::class);
}
public function values()
{
return $this->belongsToMany(Stock::class)->select('value');
}
}
and in my blade I got the values like this:
<table class="table table-borderless table-striped table-vcenter font-size-sm">
<thead>
<tr>
<th class="text-left">Barcode</th>
#foreach($product->attributes as $attribute)
<th class="text-center">{{ $attribute->name }}</th>
#endforeach
<th class="text-right">Condition</th>
<th class="text-right">Updated</th>
</tr>
</thead>
<tbody>
#forelse($product->stocks as $stock)
<tr>
<td class="text-left">
<strong>{{ $stock->barcode }}</strong>
</td>
#foreach($product->attributes as $attribute)
<td class="text-center">{{ $attribute->getValueByBarcode($stock->barcode) }}</td>
#endforeach
<td class="text-right">
#switch($stock->condition)
#case('Great')
<span class="badge badge-success"><i class="fa fa-check-circle mr-1"></i>Great</span>
#break
#case('Damaged')
<span class="badge badge-warning"><i class="fa fa-exclamation-circle mr-1"></i>Damage</span>
#break
#case('Broken')
<span class="badge badge-danger"><i class="fa fa-times-circle mr-1"></i>Broken</span>
#break
#endswitch
</td>
<td class="text-right">{{ $stock->updated_at->format('M d, Y') }}</td>
</tr>
#empty
<tr><td colspan="100" class="text-center">No Stock Available</td></tr>
#endforelse
</tbody>
</table>
This is the result of that change
I finally managed to get what I wanted but is there a cleaner way of doing the same thing?

I would use keyBy on your collection of stock attributes (Untested)
#php($s_attributes = $stock->attributes->keyBy('attribute_id'))
#foreach($product->attributes as $p_attribute)
<td class="text-center">{{ isset($s_attributes[$p_attribute->id]) ? $s_attributes[$p_attribute->id]->pivot->value : ''}}</td>
#endforeach

Related

How do i count for a specific data in a column in Laravel?

I have two models, One is Employees and other one is Skill. One Employee can have many skills. Now I'm trying to show number of employees that possesses each consecutive skill in skills view page.
My Skills View Page:
<tbody>
#foreach ($skills as $skill)
<tr>
<th scope="row">{{ $loop->index+1 }}</th>
<td>{{ $skill->skill_name }}</td>
<td>{{ $skill->totalEmp($skill->id) }}</td>
<td style="width: 25%;">
<button class="btn btn-outline-danger" type="button" title="Delete Skill">Delete Skill</button>
</td>
</tr>
#endforeach
</tbody>
Skills Model:
class Skill extends Model
{
use HasFactory;
protected $fillable = [
'skill_name'
];
public function Employee()
{
return $this->hasMany(Employees::class, 'skill_id', 'id');
}
public function totalEmp($id){
return Employees::where('status', 1)->where('skill_id','=',$id)->count();
}
}
Employees Model:
class Employees extends Model
{
use HasFactory;
protected $guarded = [];
protected $table = 'employees';
public function Skills(){
return $this->hasMany(Skill::class);
}
}
Employee table has->skill_id, other irreverent fields || Skill Model has->id and skill_name field.
You can use withCount method for this.
https://laravel.com/docs/9.x/eloquent-relationships#counting-related-models
On your Skill model you have Employee relationship defined.
So you will be able to do something like this:
$skills = Skill::withCount(['Employee'])->get();
// Prints out number of employees that posses first skill
echo $skills[0]->employee_count;
Of course you can iterate through $skills with for loop, or foreach and similar, or even use collection methods.
<tbody>
#foreach ($skills as $skill)
<tr>
<th scope="row">{{ $loop->index+1 }}</th>
<td>{{ $skill->skill_name }}</td>
<td>{{ $skill->employee_count }}</td>
<td style="width: 25%;">
<button class="btn btn-outline-danger" type="button" title="Delete Skill">Delete Skill</button>
</td>
</tr>
#endforeach
</tbody>
If you still want to use your method totalEmp you can modify it like this:
public function totalEmp(){
return $this->employee()->count();
}
One other thing that I strongly recommend is to make sure you name your models and relationships properly.
Models should be named like: Skill and Employee
Relationships should be named like: employees() and skills()
Please see example here: https://laravel.com/docs/9.x/eloquent-relationships#many-to-many

How to count all the products that belongs to category(slug) Laravel 8

I am a beginner in Laravel. I need a little help.
I have an index.blade.php file which displays all the category names. When I click on on it will generate the slug link where I have all the products that in the category. So I would like to count only those products that is belongs to category's slug and display the number on the index.blade.php. Thanks for the help.
Route::get('view-category/{slug}', [ProductsController::class,'viewcategory']);
productsController:
public function viewcategory($slug){
if(Category::where('slug', $slug)->exists()){
$category = Category::where('slug', $slug)->first();
$products = Products::where('cateId', $category->id)->where('status','1')->get();
return view('admin.products.display', compact('category','products'));
}
else{
return redirect('/dashboard')->with('status',"Slug does not exist");
}
}
category Model:
class Category extends Model
{
use HasFactory;
protected $table = "categories";
protected $fullable = [
'name',
'slug',
'description',
'status',
'popular',
];
public function products(){
return $this->belongsTo(Products::class, 'id', 'cateId');
}
}
index.blade.php:
<thead>
<tr>
<th></th>
<th>Product Name</th>
<th>Category</th>
<th>Sub Category</th>
<th>Variations</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
#foreach($category as $item)
<tr>
<td class="control" tabindex="0"></td>
<td>{{$item->products->productName}}</td>
<td>{{$item->name}}</td>
<td>{{$item->subCategory}}</td>
<td>{{$item->counter}}</td>
<td></td>
<td></td>
</tr>
#endforeach
</tbody>
</table>
You can still call relationships inside your blade files, so if you have a products relationship setup correctly, you only need to change your index blade to this
<td>{{$item->products()->count()</td>
If you have categories that don't have any products put this in your blade to check before showing the count (Its an if else statement just inline)
<td>{{$item->products ? $item->products()->count() : 'N/A'}}</td>
in your blade file add this line before your foreach
#if( $category->posts->count() )
//your #foreach code here
#endif

How to Generate Leave Balance Table for a particular employee using Laravel

In my Laravel-5.8 project, when an employee logs in I want to display a table as shown below that shows his Leave Balance
I have 3 tables that are applicable
class LeaveCategory extends Model
{
protected $table = 'leave_categories';
protected $fillable = [
'leave_category_name',
];
public function leavecategorydetail()
{
return $this->hasMany('App\Models\LeaveCategoryDetail');
}
}
class LeaveCategoryDetail extends Model
{
protected $table = 'leave_category_details';
protected $fillable = [
'id',
'leave_category_id',
'employment_type_id',
'no_of_days',
];
public function leavecategory()
{
return $this->belongsTo('App\Models\LeaveCategory', 'leave_category_id', 'id');
}
public function employmenttype()
{
return $this->belongsTo('App\Models\EmploymentType', 'employment_type_id', 'id' );
}
}
class LeaveRequest extends Model
{
protected $table = 'leave_requests';
protected $fillable = [
'id',
'employee_id',
'leave_category_id',
'leave_status',
'approved_days',
];
public function employee()
{
return $this->belongsTo('App\Models\Employee','employee_id');
}
public function leavetype()
{
return $this->belongsTo('App\Models\LeaveCategory','leave_category_id');
}
}
As earlier said, the expected result is to have 4 columns (Leave Category
, Applicable Leave, Approved Leave
, Available
)
Controller
public function leave_balance()
{
$userId = Auth::user()->id;
$userCompany = Auth::user()->company_id;
$employmentcategory = Employee::select('employeement_category_id')->where('employee_id', $userId)->where('is_active', 1)->first();
//Leave Category
$leavecategories = LeaveCategory::select('leave_category_name')->where('company_id', $userCompany)->get();
//Applicable Leave
$applicableleaves = DB::table('leave_categories')
->join('leave_category_details', 'leave_category_details.leave_category_id', '=', 'leave_categories.id')
->select('leave_category_details.no_of_days')
->where('leave_categories.company_id', $userCompany)
->where('leave_categories.employment_category_id',$employmentcategory)
->get();
//Approved Leave
$approvedleaves = DB::table('leave_requests')
->select('employee_id','leave_category_id', DB::raw('SUM(approved_days) AS approvedLeave'))
->where('employee_id', $userId)
->where('leave_category_id', $employmentcategory)
->where('leave_status',4)
->groupBy('employee_id', 'leave_category_id')
->get();
//Available
$availableleaves = $applicableleaves - $approvedleaves
$leavebalances = ...
return view('leave-balances')
->with('leavebalances', $leavebalances)
}
How do I combine the four queries in my controller ($leavecategories, $applicableleaves, $approvedleaves, $availableleaves) into $leavebalances and also get a view like
See the leave balance image
<thead>
<tr>
<th width="55%">
Leave Category
</th>
<th width="15%">
Applicable Leave
</th>
<th width="15%">
Approved Leave
</th>
<th width="15%">
leavebalances
</th>
</tr>
</thead>
<tbody>
<!--start foreach-->
<td>
</td>
<td>
</td>
<td>
</td>
<td>
</td>
<!--end foreach-->
If there is no field/value for $approvedleaves, it should initialize with 0
Thank you.
bro you can try make the two querys in one like this is not perfect but you get the idea
$all = DB::table('leave_category_details')
->leftJoin('leave_categories', function($join)
{
$join->on('leave_categories.id', '=', 'leave_category_details.leave_category_id');
$join->on('leave_categories.employment_category_id',$employmentcategory);
$join->on('leave_categories.company_id','=', $userCompany);
})
->leftJoin('leave_requests', function($join)
{
$join->on('leave_requests.employee_id', '=', $userId);
$join->on('leave_requests.leave_category_id', $employmentcategory);
$join->on('leave_status',4);
})
->groupBy('leave_requests.employee_id', 'leave_requests.leave_category_id')
->select('leave_category_details.no_of_days','leave_requests.employee_id','leave_requests.leave_category_id', DB::raw('SUM(leave_requests.approved_days) AS approvedLeave'),DB::raw('SUM(leave_requests.approved_days) - leave_category_details.no_of_days AS availableleaves')
->get();
Why get each column in a separate query when you can get them all in one.
I adjusted your controller like so:
public function leave_balance()
{
$userId = Auth::user()->id;
$userCompany = Auth::user()->company_id;
$employmentCategoryId = Employee::where('employee_id', $userId)->where('is_active', 1)->value('employeement_category_id');
//Leave Table
$leaveTable = DB::table('leave_categories as lc')
->join('leave_category_details as lcd', 'lcd.leave_category_id', '=', 'lc.id')
->join('leave_requests as lr', 'lr.leave_category_id', '=', 'lc.id')
->select('lcd.no_of_days as applicableLeaves ','lc.leave_category_name as leaveCategory',DB::raw('SUM(lr.approved_days) AS approvedLeaves'))
->where([
['lc.company_id', $userCompany],
['lc.employment_category_id',$employmentCategoryId],
['lr.employee_id', $userId],
['lr.leave_status',4]
])
->groupBy('leaveCategory', 'applicableLeaves')
->get();
return view('leave-balances')
->with('leaveTable', $leaveTable)
}
$availableleaves can be calculated in the view no need to send it.
Now in your view you display them like this:
<thead>
<tr>
<th width="55%">
Leave Category
</th>
<th width="15%">
Applicable Leave
</th>
<th width="15%">
Approved Leave
</th>
<th width="15%">
leavebalances
</th>
</tr>
</thead>
<tbody>
#foreach ($leaveTable as $tableRow)
<tr>
<td>
{{$tableRow->leaveCategory}}
</td>
<td>
{{$tableRow->applicableLeaves}}
</td>
<td>
#if(!$tabelRow->approvedLeaves->isEmpty()){{$tabelRow->approvedLeaves}}#else 0 #endif
</td>
<td>
{{($tableRow->applicableLeaves - $tabelRow->approvedLeaves)}}
</td>
</tr>
#endforeach
</tbody>
HTML is not my strongest field so your table might not show as wanted but i think its all good.

Laravel view won't loop model even though it is populated

I'm creating a web based interface for my dovecot database.
I can get a list of all the virtual domains in the database and the number of emails and aliases easily enough.
But when I try to load a page to list the emails under a specific domain, it goes weird.
Three simple models:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class VirtualDomain extends Model
{
public function emails()
{
return $this->hasMany('App\VirtualUser', 'domain_id');
}
public function aliases()
{
return $this->hasMany('App\VirtualAlias', 'domain_id');
}
}
class VirtualUser extends Model
{
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password',
];
}
class VirtualAlias extends Model
{
//
}
My default controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\VirtualDomain;
use App\VirtualUser;
class HomeController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth');
}
/**
* #return \Illuminate\Http\Response
*/
public function index()
{
return view('home', [
'domains' => VirtualDomain::all(),
]);
}
public function domain($name)
{
$domain = VirtualDomain::where('name', $name)->first();
return view('domain', [
'domain' => $domain,
'emails' => VirtualUser::where('domain_id', $domain->id),
]);
}
}
and a couple of simple blades
home.blade.php
<p>
{{ $domains->count() }} domains
</p>
<table class="table-summary">
<thead>
<tr>
<th>name</th>
<th>emails</th>
<th>aliases</th>
<th class="table-summary-options">options</th>
</tr>
</thead>
<tbody>
#foreach ($domains as $domain)
<tr>
<td><a title="view emails for this domain" href="domain/{{ $domain->name }}">{{ $domain->name }}</a></td>
<td>{{ $domain->emails->count() }}</td>
<td>{{ $domain->aliases->count() }}</td>
<td class="table-summary-options"><a class="ui-action" title="remove this domain" href=""><img src="/img/ui/remove.png" alt="remove"></a></td>
</tr>
#endforeach
</tbody>
</table>
and domain.blade.php
<p>
<< - {{ $domain->name }} - {{ $emails->count() }} emails
</p>
<table class="table-summary">
<thead>
<tr>
<th>email</th>
<th class="table-summary-options">options</th>
</tr>
</thead>
<tbody>
#foreach ($emails as $email)
<tr>
<td><a title="view aliases for this domain" href="email/{{ $email->email }}">{{ $email->email }}</a></td>
<td class="table-summary-options"><a class="ui-action" title="remove this email" href=""><img src="/img/ui/remove.png" alt="remove"></a></td>
</tr>
#endforeach
</tbody>
</table>
The view outputs the correct number of emails under the domain with {{ $emails->count() }} - but the#foreach ($emails as $email)` does not loop.
When I modify the blade to simple use the emails from the domain variable ({{ $domain->emails->count() }} and #foreach ($domain->emails as $email)), I get the right count and the list is populated correctly.
What's making it go wrong when using the emails variable?
You have to make a small change for it to work
public function domain($name)
{
$domain = VirtualDomain::where('name', $name)->first();
return view('domain', [
'domain' => $domain,
'emails' => VirtualUser::where('domain_id', $domain->id)->get(),
]);
}
Without ->get() you will get a query builder instance while with get() will return a collection. In the foreach loop a collection can be iterated while a query builder instance can't be.
Hope this helps

Many to Many form binding

How to binding a input with many to many relationship data?
My relationship is: a Model has many Damages, and a Damage has many Models. in pivot table exists a price field.
I need populate a input with price data.
{{ Form::input('number', "prices[{$model->id}][{$damage->id}]") }}
My Model:
class Model extends \BaseModel {
public function damages()
{
return $this->belongsToMany('Damage', 'prices_damages', 'model_id', 'damage_id')
->withPivot('price')
->withTimestamps();
}
}
Pivot table
Schema::create('prices_damages', function(Blueprint $table)
{
$table->increments('id');
$table->integer('model_id')->unsigned();
$table->integer('damage_id')->unsigned();
$table->float('price')->nullable();
$table->timestamps();
});
Controller
/**
* Display a index dashboard page.
*
* #return \Illuminate\Http\Response
*/
public function getDamages()
{
$models = \Model::orderBy('order')->get();
$damages = \Damage::orderBy('order')->get();
return $this->render('Avarias', 'prices.damages', compact('models', 'damages'));
}
View:
<table class="table-striped table-header-rotated">
<thead>
<tr>
<th></th>
#foreach ($damages as $damage)
<th class="vertical"><div><span>{{ $damage->name }}</span></div></th>
#endforeach
</tr>
</thead>
<tbody>
#foreach ($models as $model)
<tr>
<td>{{ $model->fullname }}</td>
#foreach ($damages as $damage)
<td>
{{ Form::input('number', "prices[{$model->id}][{$damage->id}]", null, ['min' => 0, 'step' => 0.01]) }}
</td>
#endforeach
</tr>
#endforeach
</tbody>
</table>
You can't bind a collection (in the sense of laravel form model binding), so you can do this:
#foreach ($model->damages as $damage)
{{ Form::input('number', "damages[{$damage->id}][price]", $damage->pivot->price) }}
#endforeach

Resources