Laravel E-commerce Product Model With Price and Discount Concept.

Laravel Concept E-commerce Product Model

Let’s create a Multi-languages product table, model, and controller, and Calculate the price according to a discount for a period of time automatically.

We will focus in this tutorial on creating the product model only. If you want to learn more about Laravel localization and its middleware and category model. please check this article here Laravel 9 Localization With Multi-languages Database Tutorial

What we are going to code:

  • Multi-languages product table so we can add as many translations as we want.
  • product reference table for storing price, discount, and other shared data between all translations.
  • Adding an attribute for calculating the current price for a product.
  • Creating Laravel API resources for a better JSON response.

Creating Product Tables.

We will create two tables the first one for store the product data in multi-language such as Arabic and English and we will name this table ‘products’ then we create the table for storing the shared data such as price, discount, and images and we name it ‘product_refs’.

php artisan make:model ProductRef -m
php artisan make:model Product -m

The above command will create the models and migrations for our tables.

Let’s create our tables’ columns:

The product table will store the product’s name and slug and all SEO inputs plus the images.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->bigIncrements('id')->index();
            $table->string('lang_id')->nullable();
            $table->string('name')->nullable();
            $table->string('slug')->nullable();
            $table->text('video_url')->nullable();
            $table->string('image')->nullable();
            $table->string('thumbnailsm')->nullable();
            $table->string('thumbnailmd')->nullable();
            $table->string('thumbnailxl')->nullable();
            $table->string('cover')->nullable();
            $table->string('cothumbnailsm')->nullable();
            $table->string('cothumbnailmd')->nullable();
            $table->string('cothumbnailxl')->nullable();
            $table->string('alt_image')->nullable();
            $table->string('alt_cover')->nullable();
            $table->string('short_decription')->nullable();
            $table->string('keywords')->nullable();
            $table->string('robot')->nullable();
            $table->longText('description')->nullable();
            $table->unsignedBigInteger('ref_id')->nullable() ;
            $table->unsignedBigInteger('user_id');
            $table->string('status')->nullable();
            $table->timestamp('publish_date')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('products');
    }
};

The product reference will store all the shared data.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('product_refs', function (Blueprint $table) {
            $table->bigIncrements('id'); 
            $table->string('status')->nullable();
            $table->decimal('price', $precision = 8, $scale = 2)->nullable();
            $table->decimal('discount', $precision = 8, $scale = 2)->nullable();
            $table->decimal('shipping_fee', $precision = 8, $scale = 2)->nullable();
            $table->timestamp('discount_start')->nullable();
            $table->timestamp('discount_end')->nullable();
            $table->unsignedInteger('stock')->nullable() ;
            $table->string('barcode')->nullable();
            $table->unsignedInteger('sort')->nullable();
            $table->unsignedInteger('featured')->nullable();
            $table->unsignedInteger('views')->nullable();
            $table->unsignedInteger('rate')->nullable();
            $table->unsignedInteger('rate_count')->nullable();
            $table->unsignedBigInteger('brand_id')->nullable();
            $table->unsignedBigInteger('category_id')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('product_refs');
    }
};

Let’s migrate all the tables.

php artisan migrate

Product Models

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;


class Product extends Model 
{
    use HasFactory;
    protected $guarded =['id'];

    public function ref()
    {
        return $this->belongsTo(ProductRef::class,'ref_id');
    }


    public function user()
    {
        return $this->belongsTo(User::class,'user_id');
    }

    public function getCategoryAttribute()
    {
         $categoryRef= $this->ref->category;
         return $categoryRef->children()->where('lang_id',$this->lang_id)
         ->first(['name','slug','id']);
    }

   
}

Let’s explain the product model code.

  • ref() for getting the product’s shared data from prodcut_refs table using ref_id.
  • user() for the admin who made the last update.
  • getCategoryAttribute() to return the product’s category.
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;

class ProductRef extends Model implements HasMedia
{
    use HasFactory;
    protected $guarded =['id'];
    public function childern()
    {
        return $this->hasMany(Product::class,'ref_id');
    }


    public function category()
    {
        return  $this->belongsTo(categoryRef::class, 'category_id');
        
    }


   public function getHasDiscountAttribute()
  {
    
    if(!empty($this->discount)){
          $check = Carbon::now()->between($this->discount_start,$this->discount_end);
    }
    return $this->discount;
    
   }

    public function getPriceForSellingAttribute()
   {
    if($this->HasDiscount) { return ($this->price - ($this->price * $this->discount/100));} ;
    return $this->price;

   }

   public function getNameAttribute(){
    $item_name = $this->childern()->where('lang_id',app()->getLocale())->first('name');
    return $item_name?->name;
   }
}

The productRef class has all the work, let’s explain.

  • childern() it’s for getting all the available translations of the product.
  • category() to return the product category.
  • getHasDiscountAttribute() for checking if the product is in a discount duration.
  • PriceForSelling() it gets the current price after the discount or the product price to sell.
  • getNameAttribute() it’s get’s the product name according to the app language, it will be used for the admin panel more than the end user.

Product API Resources

Let’s create the API resource for our product model for a better JSON response.

php artisan make:resource ProductResource

It will create the resource file app\Http\Resources\ProductResource.php . Let’s modify our file as the following.

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;
use Storage;
use  App\Models\Product;

class ProductResource extends JsonResource
{
   
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'lang_id' => $this->lang_id,
            'slug' => $this->slug,
            'name' => $this->name,
            'category' => $this->Category,
            'image' => $this->image? Storage::disk('imagesfp')->url($this->image):'',
            'thumbnailsm' => $this->thumbnailsm? Storage::disk('imagesfp')->url($this->thumbnailsm):'',
            'thumbnailmd' => $this->thumbnailmd? Storage::disk('imagesfp')->url($this->thumbnailmd):'',
            'thumbnailxl' => $this->thumbnailxl? Storage::disk('imagesfp')->url($this->thumbnailxl):'',
            'cover' => $this->cover? Storage::disk('imagesfp')->url($this->cover):'',
            'cothumbnailsm' => $this->cothumbnailsm? Storage::disk('imagesfp')->url($this->cothumbnailsm):'',
            'cothumbnailmd' => $this->cothumbnailmd? Storage::disk('imagesfp')->url($this->cothumbnailmd):'',
            'cothumbnailxl' => $this->cothumbnailxl? Storage::disk('imagesfp')->url($this->cothumbnailxl):'',
            'alt_image' => $this->alt_image,
            'alt_cover' => $this->alt_cover,
            'short_decription' => $this->short_decription,
            'key_words' => $this->key_words,
            'robot' => $this->robot,
            'description' => $this->description,
            'publish_date' => $this->publish_date,
            'sort' => $this->ref->sort,
            'featured' => $this->ref->featured,
            'views' => $this->ref->views,
            'rate' => $this->ref->rate,
            'rate_count' => $this->ref->rate_count,
            'status' => $this->status,
            'user_id' => $this->user_id,
            'user_name' => $this->user?->FullName,
            'updated_at' => $this->updated_at,
            'refName' => $this->ref?->Name,
            'price' => $this->ref?->price,
            'priceForSelling' => $this->ref?->PriceForSelling,
            'hasDiscount' => $this->ref?->HasDiscount,
            'discount' => $this->ref?->discount,
            'discount_start' =>$this->ref->discount_start?
 date('Y-m-d\TH:i:s', strtotime($this->ref->discount_start)):"" ,
            'discount_end' => $this->ref->discount_end? 
date('Y-m-d\TH:i:s', strtotime($this->ref->discount_end)):"" ,
            'barcode' => $this->ref?->barcode,
            'stock' => $this->ref?->stock,
            'updated_at' => $this->updated_at,
            'translations'=> Product::where('ref_id',$this->ref_id)->
where('id','!=',$this->id)->get(['id','lang_id']),
        ];
    }
}

Let’s explain the API resource:

  • we create a property for every single data we need.
  • We get the category by getCategoryAttribute() in the product model.
  • We used the Storage class for getting the URLs of the images.
  • priceForSelling is for the current price of the product from the getPriceForSellingAttribute() of ProductRef model.
  • We have a flag here hasDiscount so we can know if there is a discount applied to the product price.
  • Finally, we get the translation in translations

To make sure your website is fast you should cache the data. so you don’t have to do all the queries and processing with every single request. learn more about using Laravel cache here Laravel Cache Redis And File Store Tutorial With Example.

One thought on “Laravel E-commerce Product Model With Price and Discount Concept.”

Leave a Reply

Your email address will not be published. Required fields are marked *