From b00378ba8f4e3fd98d4738dee6fe6b16d84fea0f Mon Sep 17 00:00:00 2001 From: bayu Date: Tue, 30 Dec 2025 16:08:16 +0700 Subject: [PATCH] product grid --- app/Models/Discount.php | 33 ++ app/Models/DiscountItem.php | 23 + app/Models/ItemImage.php | 13 + app/Models/ItemReference.php | 203 +++++++++ app/Models/ItemVariant.php | 53 +++ app/Models/Items.php | 162 +++++-- app/View/Components/Home/ProductHighlight.php | 29 ++ composer.json | 1 + composer.lock | 64 ++- .../home/product-highlight.blade.php | 88 ++++ resources/views/home/fashion-v1.blade.php | 424 +----------------- 11 files changed, 634 insertions(+), 459 deletions(-) create mode 100644 app/Models/Discount.php create mode 100644 app/Models/DiscountItem.php create mode 100644 app/Models/ItemImage.php create mode 100644 app/Models/ItemReference.php create mode 100644 app/Models/ItemVariant.php create mode 100644 app/View/Components/Home/ProductHighlight.php create mode 100644 resources/views/components/home/product-highlight.blade.php diff --git a/app/Models/Discount.php b/app/Models/Discount.php new file mode 100644 index 0000000..e7acd55 --- /dev/null +++ b/app/Models/Discount.php @@ -0,0 +1,33 @@ +hasMany(DiscountItem::class); + } + + public function customerGroup() { + return $this->belongsTo(CustomerGroup::class); + } + + public function location() { + return $this->belongsTo(Location::class); + } +} diff --git a/app/Models/DiscountItem.php b/app/Models/DiscountItem.php new file mode 100644 index 0000000..e6e6072 --- /dev/null +++ b/app/Models/DiscountItem.php @@ -0,0 +1,23 @@ +belongsTo(ItemReference::class); + } + + public function discount() + { + return $this->belongsTo(Discount::class); + } +} diff --git a/app/Models/ItemImage.php b/app/Models/ItemImage.php new file mode 100644 index 0000000..7b9ca68 --- /dev/null +++ b/app/Models/ItemImage.php @@ -0,0 +1,13 @@ +hasMany(ItemImage::class, 'item_id', 'item_id'); + } + + public function item() + { + return $this->belongsTo(Items::class)->with("brand", "gender", "category"); + } + + public function itemVariant() + { + return $this->belongsTo(ItemVariant::class); + } + + public function variants() + { + $model = ItemVariant::where('item_id', $this->item_id)->get(); + return $model; + } + + public function location() + { + return $this->belongsTo(Location::class, "location_id"); + } + + public function stocks() + { + return $this->hasMany(Stock::class, ["item_id", "item_variant_id"], ["item_id", "item_variant_id"]) + ->with("location"); + } + + public function bucketStocks() + { + return $this->hasMany(BucketStock::class)->with("bucket"); + // ->where("quantity",">",0); + } + + public function promotion() + { + return $this->belongsToMany(Promotion::class, "promotion_item", "item_id", "promotion_id"); + } + + public function price() + { + $user = auth()->user(); + + [$location_id, $is_consignment] = Cache::remember( + "employee_user_" . optional($user)->id, + 60, + function () use ($user) { + if (!$user) { + return [10, false]; + } + + $employee = $user->employee; + $location_id = $employee->location_id ?? 10; + $location = $employee->location; + + $is_consignment = (bool) optional($location)->is_consignment + && optional($location)->id != 9; + + return [$location_id, $is_consignment]; + } + ); + + return $this->hasOne(Discount::class, 'id', 'item_reference_id') + ->leftJoin('discount_items', 'discount_items.discount_id', '=', 'discounts.id') + ->where('discounts.type', 'price') + ->where(function ($query) { + $query->where('valid_at', '<=', now()) + ->orWhereNull('valid_at'); + }) + ->where(function ($query) { + $query->where('expired_at', '>', now()) + ->orWhereNull('expired_at'); + }) + ->where(function ($query) use ($location_id, $is_consignment) { + if (!$is_consignment) { + $query->whereNull('discounts.location_id'); + } + + if ($location_id) { + $query->orWhere('discounts.location_id', $location_id); + } + }) + ->orderByDesc('discounts.created_at') + ->select([ + 'discount_items.item_reference_id', + 'discount_items.price', + ]); + } + + + + public function discount() + { + + $user = auth()->user(); + list($location_id, $is_consignment) = Cache::remember("employee_user_".$user->id, 60 * 60 * 24, function(){ + + $employee = @$user->employee; + $location_id = @$employee->location_id ?? 10; + $location = @$employee->location; + $is_consignment = (boolean) @$location->is_consignment && $location->id != 9; + return [$location_id, $is_consignment]; + + }); + +// Log::info([$location_id, $is_consignment]); + + return $this->hasOne(Discount::class,DB::raw("discount_items.item_reference_id")) + ->leftJoin("discount_items","discounts.id","=","discount_id") + ->where("discounts.type","discount") + ->orderBy("discounts.created_at","desc") + ->where(function($query){ + $query->where("valid_at", "<=", Carbon::now()) + ->orWhereNull("valid_at"); + }) + ->where(function($query){ + $query->where("expired_at", ">", Carbon::now()) + ->orWhereNull("expired_at"); + }) + + ->where(function($query) use ($location_id, $is_consignment){ + if (!$is_consignment) + $query->whereNull("discounts.location_id"); + if ($location_id) + $query->orWhere("discounts.location_id", $location_id); + }) + ->select("item_reference_id","price"); + } + + + public function point() + { + return $this->hasOne(CustomerPointRule::class); + } + + public function promotion3() + { + $now = Carbon::now()->format('Y-m-d'); + $promotions = "select DISTINCT ON (item_id) item_id as item_reference_id, name, note from + promotions left join promotion_item on promotions.id = promotion_id + where (valid_date is null or valid_date::date <= '$now') + and (expired_date is null or expired_date::date >= '$now') + and type <> 'POINT'"; + + $points = "select item_reference_id, array_agg((case when qty is null then '' else (qty || ': ') end) || point || ' Point' order by point asc) as name + from customer_point_rules + group by item_reference_id"; + + return $this->hasOne(ItemReference::class,"id") + ->select('item_reference.id as item_reference_id',DB::raw("(case when points.name is null then promotions.name else array_to_string(points.name,', ') end) as promotion"),'promotions.note as promotion_note') + ->leftJoin(DB::raw("(" . $promotions . ") as promotions"), "promotions.item_reference_id", "=", "item_reference.id") + ->leftJoin(DB::raw("(" . $points . ") as points"), "points.item_reference_id", "=", "item_reference.id"); + } + + public function promotion2() + { + $now = Carbon::now()->format('Y-m-d'); + $promotions = "select DISTINCT ON (item_id) item_id, name, note from + promotions left join promotion_item on promotions.id = promotion_id + where (valid_date is null or valid_date::date <= '$now') + and (expired_date is null or expired_date::date >= '$now') + and item_id = $this->id + and type <> 'POINT'"; + + $points = "select item_reference_id, array_agg((case when qty is null then '' else (qty || ': ') end) || point || ' Point' order by point asc) as name + from customer_point_rules + where item_reference_id = $this->id + group by item_reference_id"; + + $result = DB::table('item_reference') + ->select(DB::raw("(case when points.name is null then promotions.name else array_to_string(points.name,', ') end) as promotion"), + 'promotions.note as promotion_note') + ->leftJoin(DB::raw("(" . $promotions . ") as promotions"), "promotions.item_id", "=", "item_reference.id") + ->leftJoin(DB::raw("(" . $points . ") as points"), "points.item_reference_id", "=", "item_reference.id") + ->where('item_reference.id', $this->id) + ->first(); + + return $result; + } +} diff --git a/app/Models/ItemVariant.php b/app/Models/ItemVariant.php new file mode 100644 index 0000000..5659009 --- /dev/null +++ b/app/Models/ItemVariant.php @@ -0,0 +1,53 @@ +belongsTo(ItemReference::class, ["item_id", "id"], ["item_id", "item_variant_id"]); + } + + + public function variables() + { + return $this->belongsToMany(Variant::class, "variant_value")->select("id", "name", "value"); + } + + public function image() + { + return $this->hasOne(ItemImage::class)->where('item_id', $this->item_id); + } + + public function images() + { + return $this->hasMany(ItemImage::class)->where('item_id', $this->item_id); + } + public function variantValue() + { + return $this->hasMany(\App\Models\VariantValue::class, 'item_variant_id'); + } +} diff --git a/app/Models/Items.php b/app/Models/Items.php index 07351b4..c96f0c2 100644 --- a/app/Models/Items.php +++ b/app/Models/Items.php @@ -8,15 +8,17 @@ use Cviebrock\EloquentSluggable\Sluggable; use Illuminate\Database\Eloquent\SoftDeletes; use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; -use DB; -// use Awobaz\Compoships\Compoships; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\DB; +use Awobaz\Compoships\Compoships; use Spatie\Activitylog\LogOptions; -use Cache; + +use Illuminate\Support\Facades\Storage; class Items extends Model { use HasFactory, SoftDeletes; - // use Compoships; + use Compoships; public function getActivitylogOptions(): LogOptions { @@ -95,7 +97,7 @@ class Items extends Model { return $this->hasMany(ItemVariant::class,"item_id")->leftJoin("stocks","item_variant_id","=","item_variants.id") ->select("item_variants.id","item_variants.code","item_variants.display_name","item_variants.is_publish", DB::raw("SUM(quantity) as stock"),"description","item_variants.item_id") - ->groupBy("item_variants.id","item_variants.code","description","item_variants.item_id"); + ->groupBy("item_variants.id","item_variants.code","description","item_variants.item_id","item_variants.display_name","item_variants.is_publish"); } public function images() @@ -141,45 +143,89 @@ class Items extends Model public function discount() { $user = auth()->user(); - list($location_id, $is_consignment) = Cache::remember("employee_user_".$user->id, 60 * 60 * 24, function() use ($user){ - if ($user == null) - return [10, false]; - $employee = @$user->employee; - $location_id = @$employee->location_id; - $location = @$employee->location; - $is_consignment = (boolean) @$location->is_consignment; - return [$location_id, $is_consignment]; + [$location_id, $is_consignment] = Cache::remember( + 'employee_user_' . optional($user)->id, + 60 * 60 * 24, + function () use ($user) { + if (!$user) { + return [10, false]; + } - }); + $employee = $user->employee; + $location = $employee?->location; + + return [ + $employee?->location_id, + (bool) optional($location)->is_consignment, + ]; + } + ); + + + // return $this->hasOne(Discount::class,DB::raw("item_reference.item_id")) + // ->leftJoin('discount_items', 'discount_items.discount_id', '=', 'discounts.id') + // ->leftJoin('item_reference', 'item_reference.id', '=', 'discount_items.item_reference_id') + // ->where('discounts.type', 'discount') + // ->where(function ($query) { + // $query->whereNull('valid_at') + // ->orWhere('valid_at', '<=', now()); + // }) + // ->where(function ($query) { + // $query->whereNull('expired_at') + // ->orWhere('expired_at', '>=', now()); + // }) + // ->where(function ($query) use ($location_id, $is_consignment) { + // if (!$is_consignment) { + // $query->whereNull('discounts.location_id'); + // } + + // if ($location_id) { + // $query->orWhere('discounts.location_id', $location_id); + // } + // }) + // ->orderByDesc('discounts.created_at') + // ->select([ + // 'item_reference.item_id', + // 'discount_items.price', + // ]); + + return $this->hasOne(DiscountItem::class, 'item_reference_id', 'id') + ->leftJoin('discounts', 'discounts.id', '=', 'discount_items.discount_id') + ->leftJoin('item_reference', 'item_reference.id', '=', 'discount_items.item_reference_id') + ->where('discounts.type', 'discount') + ->where(function ($q) { + $q->whereNull('discounts.valid_at') + ->orWhere('discounts.valid_at', '<=', now()); + }) + ->where(function ($q) { + $q->whereNull('discounts.expired_at') + ->orWhere('discounts.expired_at', '>=', now()); + }) + ->where(function ($q) use ($location_id, $is_consignment) { + if (!$is_consignment) { + $q->whereNull('discounts.location_id'); + } + + if ($location_id) { + $q->orWhere('discounts.location_id', $location_id); + } + }) + ->orderByDesc('discounts.created_at') + ->select([ + 'item_reference.item_id', + 'discount_items.price', + ]); - return $this->hasOne(Discount::class,DB::raw("item_reference.item_id")) - ->leftJoin("discount_items","discounts.id","=","discount_id") - ->leftJoin("item_reference","item_reference.id","=","item_reference_id") - ->where("discounts.type","discount") - ->orderBy("discounts.created_at","desc") - ->where(function ($query) { - $query->whereNull("valid_at") - ->orWhereDate("valid_at", "<=", Carbon::now()); - }) - ->where(function ($query) { - $query->whereNull("expired_at") - ->orWhereDate("expired_at", ">=", Carbon::now()); - }) - ->where(function($query) use ($location_id, $is_consignment){ - if (!$is_consignment) - $query->whereNull("discounts.location_id"); - if ($location_id) - $query->orWhere("discounts.location_id", $location_id); - }) - ->select("item_id","price"); } + public function price() { $user = auth()->user(); - list($location_id, $is_consignment) = Cache::remember("employee_user_".$user->id, 60 * 60 * 24, function() use ($user){ + $userId = $user ? $user->id : 0; + list($location_id, $is_consignment) = Cache::remember("employee_user_".$userId, 60 * 60 * 24, function() use ($user){ if ($user == null) return [10, false]; @@ -229,4 +275,50 @@ class Items extends Model { return $this->hasMany(ItemAttribute::class, 'item_id'); } + + public function getImageUrlAttribute() + { + $image = $this->images->first()->filename ?? null; + + + + if (str_contains($image,"http")) { + return $image; + } + + return $image ? Storage::disk('wms')->url($image) : null; + } + + + public function conversion_value() + { + $convertion = 1; + if (($this->display_unit != $this->unit) and ($this->display_unit)){ + $convertions = DB::select("select to_qty / from_qty as conv + from item_convertions where from_unit = ? and to_unit = ?", + [$this->display_unit, $this->unit] ); + $convertion = max((float) @$convertions[0]->conv,1); + } + + return $convertion; + } + + public function getDisplayPriceAttribute() + { + $convertion = $this->conversion_value(); + + $price = @$this->variants->first()->reference->price->price ?? null; + $price = $price ? $price : $this->net_price; + + return (float) $price * $convertion; + } + + + public function getDisplayDiscountPriceAttribute() + { + $convertion = $this->conversion_value(); + + $discountPrice = @$this->discount->price ?? 0; + return (float) $discountPrice * $convertion; + } } diff --git a/app/View/Components/Home/ProductHighlight.php b/app/View/Components/Home/ProductHighlight.php new file mode 100644 index 0000000..4e50897 --- /dev/null +++ b/app/View/Components/Home/ProductHighlight.php @@ -0,0 +1,29 @@ + 'new', + ]; + $this->products = $productRepository->getList($params); + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View|Closure|string + { + return view('components.home.product-highlight'); + } +} diff --git a/composer.json b/composer.json index 319610e..ddc1f3e 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "license": "MIT", "require": { "php": "^8.2", + "awobaz/compoships": "^2.5", "laravel/framework": "^12.0", "laravel/tinker": "^2.10.1", "spatie/laravel-activitylog": "^4.10" diff --git a/composer.lock b/composer.lock index 89955fc..2657f3d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,70 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "040332991bd2bf1daab2e1f01063d4a5", + "content-hash": "39a129e1371f7351b7cd6beab65a522a", "packages": [ + { + "name": "awobaz/compoships", + "version": "2.5.4", + "source": { + "type": "git", + "url": "https://github.com/topclaudy/compoships.git", + "reference": "dcae8012a8704fc2acd8dce2d8a1b35ce292adbe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/topclaudy/compoships/zipball/dcae8012a8704fc2acd8dce2d8a1b35ce292adbe", + "reference": "dcae8012a8704fc2acd8dce2d8a1b35ce292adbe", + "shasum": "" + }, + "require": { + "illuminate/database": ">=5.6 <13.0" + }, + "require-dev": { + "ext-sqlite3": "*", + "fakerphp/faker": "^1.18", + "phpunit/phpunit": "^6.0|^8.0|^9.0|^10.0|^11.0|^12.0" + }, + "suggest": { + "awobaz/blade-active": "Blade directives for the Laravel 'Active' package", + "awobaz/eloquent-auto-append": "Automatically append accessors to model serialization", + "awobaz/eloquent-mutators": "Reusable mutators (getters/setters) for Laravel 5's Eloquent", + "awobaz/syntactic": "Syntactic sugar for named and indexed parameters call." + }, + "type": "library", + "autoload": { + "psr-4": { + "Awobaz\\Compoships\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Claudin J. Daniel", + "email": "cdaniel@awobaz.com" + } + ], + "description": "Laravel relationships with support for composite/multiple keys", + "keywords": [ + "laravel", + "laravel composite keys", + "laravel relationships" + ], + "support": { + "issues": "https://github.com/topclaudy/compoships/issues", + "source": "https://github.com/topclaudy/compoships/tree/2.5.4" + }, + "funding": [ + { + "url": "https://paypal.me/awobaz", + "type": "custom" + } + ], + "time": "2025-12-23T18:33:46+00:00" + }, { "name": "brick/math", "version": "0.13.1", diff --git a/resources/views/components/home/product-highlight.blade.php b/resources/views/components/home/product-highlight.blade.php new file mode 100644 index 0000000..85aca1b --- /dev/null +++ b/resources/views/components/home/product-highlight.blade.php @@ -0,0 +1,88 @@ +
+

This week's highlights

+ + +
+
+ +
+
+ + +
+ + + @foreach ($products as $key => $product) +
+
+
+ Sale + + +
+ + +
+
+ {{--
+
+ XS + S + M + L + +
+
--}} +
+ +
Rp {{ number_format($product->display_price,0,",",".") }} @if ($product->display_discount_price > 0)Rp {{ number_format($product->display_discount_price,0,",",".") }}@endif
+
+ @if ($product->variants->count() > 1) +
+{{ $product->variants->count() - 1 }} Varian
+ @endif + {{--
+1 color
--}} + {{--
+ + + + +
--}} +
+
+
+ @endforeach + +
+
diff --git a/resources/views/home/fashion-v1.blade.php b/resources/views/home/fashion-v1.blade.php index b9b56d8..4f038a7 100644 --- a/resources/views/home/fashion-v1.blade.php +++ b/resources/views/home/fashion-v1.blade.php @@ -1345,429 +1345,7 @@ -
-

This week's highlights

- - -
-
- -
-
- - -
- - -
-
-
- Sale - - -
- Image -
-
-
-
- XS - S - M - L - -
-
-
- -
$126.50 $156.00
-
-
+1 color
-
- - - - -
-
-
-
- - -
-
-
- - -
- Image -
-
-
-
- 6.5 - 7 - 7.5 - 8 - -
-
-
- -
$74.00
-
-
+2 colors
-
- - - - - - -
-
-
-
- - -
-
-
- - -
- Image -
-
-
-
- S - M - L - XL - -
-
-
- -
$32.99
-
-
+2 colors
-
- - - - - - -
-
-
-
- - -
-
-
- - -
- Image -
-
-
- -
$105.00
-
-
+1 color
-
- - - - -
-
-
-
- - -
-
-
- - -
- Image -
-
-
-
- XS - S - M - L - -
-
-
- -
$38.50
-
-
+1 color
-
- - - - -
-
-
-
- - -
-
-
- - -
- Image -
-
-
- -
$140.00
-
-
+2 colors
-
- - - - - - -
-
-
-
- - -
-
-
- -17% - - -
- Image -
-
-
- -
$96.00 $112.00
-
-
+2 colors
-
- - - - - - -
-
-
-
- - -
-
-
- - -
- Image -
-
-
-
- M - L - XL -
-
-
- -
$27.00
-
-
+3 colors
-
- - - - - - - - -
-
-
-
-
-
- +