From f657a5273f670ab064ff77b38e8e707e2a25d94a Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 2 Jan 2026 13:50:19 +0700 Subject: [PATCH] feat: Add Redemption and Reward factories, refactor existing factories to use explicit `with` methods, and update UserSeeder. --- app/Models/Bottle.php | 20 +++---- app/Models/Category.php | 10 ++-- app/Models/Customer.php | 10 ++-- app/Models/Membership.php | 20 +++---- app/Models/Order.php | 30 +++++----- app/Models/OrderItem.php | 10 ++-- app/Models/Outlet.php | 46 +++++++------- app/Models/Payroll.php | 10 ++-- app/Models/Perfume.php | 20 +++---- app/Models/Product.php | 20 +++---- app/Models/Tier.php | 30 +++++----- app/Models/User.php | 73 +++++++++++------------ app/Models/Warehouse.php | 10 ++-- database/factories/BottleFactory.php | 8 ++- database/factories/CategoryFactory.php | 6 +- database/factories/CustomerFactory.php | 5 ++ database/factories/EmployeeFactory.php | 12 +++- database/factories/MembershipFactory.php | 7 ++- database/factories/OrderItemFactory.php | 41 ++++++++++++- database/factories/OutletFactory.php | 17 +++++- database/factories/PaymentFactory.php | 13 +++- database/factories/PayrollFactory.php | 21 +++++-- database/factories/PerfumeFactory.php | 23 +++++-- database/factories/ProductFactory.php | 6 +- database/factories/RedemptionFactory.php | 43 +++++++++++++ database/factories/RestockFactory.php | 8 --- database/factories/RestockItemFactory.php | 8 --- database/factories/RewardFactory.php | 44 ++++++++++++++ database/factories/TierFactory.php | 6 +- database/factories/UserFactory.php | 5 ++ database/factories/VoucherFactory.php | 33 +++++++--- database/factories/WarehouseFactory.php | 14 ++--- database/seeders/CategorySeeder.php | 26 ++++---- database/seeders/OutletSeeder.php | 2 + database/seeders/UserSeeder.php | 5 ++ 35 files changed, 438 insertions(+), 224 deletions(-) create mode 100644 database/factories/RedemptionFactory.php create mode 100644 database/factories/RewardFactory.php diff --git a/app/Models/Bottle.php b/app/Models/Bottle.php index 3cc5ab5..be786c8 100644 --- a/app/Models/Bottle.php +++ b/app/Models/Bottle.php @@ -35,28 +35,28 @@ public function getSlugOptions(): SlugOptions ->saveSlugsTo('slug'); } + public function items(): MorphMany + { + return $this->morphMany(OrderItem::class, 'orderable'); + } + public function outlets(): BelongsToMany { return $this->belongsToMany(Outlet::class)->withPivot('stock'); } - public function warehouses(): BelongsToMany - { - return $this->belongsToMany(Warehouse::class)->withPivot('stock'); - } - public function purchaseItems(): MorphMany { return $this->morphMany(PurchaseItem::class, 'purchasable'); } - public function items(): MorphMany - { - return $this->morphMany(OrderItem::class, 'orderable'); - } - public function restockItems(): MorphMany { return $this->morphMany(RestockItem::class, 'restockable'); } + + public function warehouses(): BelongsToMany + { + return $this->belongsToMany(Warehouse::class)->withPivot('stock'); + } } diff --git a/app/Models/Category.php b/app/Models/Category.php index 5ad6f10..f2a2575 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -48,13 +48,13 @@ public function getSlugOptions(): SlugOptions ->saveSlugsTo('slug'); } - public function perfumes(): BelongsToMany - { - return $this->belongsToMany(Perfume::class); - } - public function articles(): BelongsToMany { return $this->belongsToMany(Article::class); } + + public function perfumes(): BelongsToMany + { + return $this->belongsToMany(Perfume::class); + } } diff --git a/app/Models/Customer.php b/app/Models/Customer.php index ff07697..40d0e72 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -37,13 +37,13 @@ public function yesterday(Builder $query): void $query->whereDate('created_at', today()->subDay()); } - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - public function orders(): HasMany { return $this->hasMany(Order::class); } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } } diff --git a/app/Models/Membership.php b/app/Models/Membership.php index 758023c..fd64235 100644 --- a/app/Models/Membership.php +++ b/app/Models/Membership.php @@ -23,16 +23,6 @@ protected function casts(): array ]; } - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - - public function tier(): BelongsTo - { - return $this->belongsTo(Tier::class); - } - #[Scope] protected function scopeForSpending(Builder $query, $totalSpending): void { @@ -43,4 +33,14 @@ protected function scopeForSpending(Builder $query, $totalSpending): void ->orWhere('max_spending', '>=', $totalSpending); }); } + + public function tier(): BelongsTo + { + return $this->belongsTo(Tier::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } } diff --git a/app/Models/Order.php b/app/Models/Order.php index 54ce9c0..67a4a33 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -52,33 +52,33 @@ public function yesterday(Builder $query): void $query->whereDate('created_at', today()->subDay()); } - public function outlet(): BelongsTo - { - return $this->belongsTo(Outlet::class); - } - - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - public function customer(): BelongsTo { return $this->belongsTo(Customer::class); } - public function voucher(): BelongsTo - { - return $this->belongsTo(Voucher::class); - } - public function items(): HasMany { return $this->hasMany(OrderItem::class); } + public function outlet(): BelongsTo + { + return $this->belongsTo(Outlet::class); + } + public function payments(): HasMany { return $this->hasMany(Payment::class); } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function voucher(): BelongsTo + { + return $this->belongsTo(Voucher::class); + } } diff --git a/app/Models/OrderItem.php b/app/Models/OrderItem.php index aca0e25..ced3b0a 100644 --- a/app/Models/OrderItem.php +++ b/app/Models/OrderItem.php @@ -41,16 +41,16 @@ public function order(): BelongsTo return $this->belongsTo(Order::class); } - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - public function orderable(): MorphTo { return $this->morphTo()->withTrashed(); } + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + public function volumes(): HasMany { return $this->hasMany(VolumeItem::class); diff --git a/app/Models/Outlet.php b/app/Models/Outlet.php index 13a8056..2d4b85e 100644 --- a/app/Models/Outlet.php +++ b/app/Models/Outlet.php @@ -45,9 +45,14 @@ public function operational(Builder $query): void $query->where('status', OutletStatus::OPERATIONAL->value); } - public function openingHours(): HasMany + public function bottles(): BelongsToMany { - return $this->hasMany(OpeningHour::class); + return $this->belongsToMany(Bottle::class)->withPivot('stock'); + } + + public function expenses(): HasMany + { + return $this->hasMany(Expense::class); } public function facilities(): HasMany @@ -55,9 +60,14 @@ public function facilities(): HasMany return $this->hasMany(Facility::class); } - public function vouchers(): BelongsToMany + public function openingHours(): HasMany { - return $this->belongsToMany(Voucher::class); + return $this->hasMany(OpeningHour::class); + } + + public function orders(): HasMany + { + return $this->hasMany(Order::class); } public function perfumes(): BelongsToMany @@ -70,29 +80,14 @@ public function products(): BelongsToMany return $this->belongsToMany(Product::class)->withPivot('stock'); } - public function bottles(): BelongsToMany - { - return $this->belongsToMany(Bottle::class)->withPivot('stock'); - } - - public function expenses(): HasMany - { - return $this->hasMany(Expense::class); - } - - public function users(): BelongsToMany - { - return $this->belongsToMany(User::class); - } - public function purchases(): HasMany { return $this->hasMany(Purchase::class); } - public function orders(): HasMany + public function restocks(): HasMany { - return $this->hasMany(Order::class); + return $this->hasMany(Restock::class); } public function stockOpname(): HasMany @@ -100,8 +95,13 @@ public function stockOpname(): HasMany return $this->hasMany(StockOpname::class); } - public function restocks(): HasMany + public function users(): BelongsToMany { - return $this->hasMany(Restock::class); + return $this->belongsToMany(User::class); + } + + public function vouchers(): BelongsToMany + { + return $this->belongsToMany(Voucher::class); } } diff --git a/app/Models/Payroll.php b/app/Models/Payroll.php index 610b347..337c25a 100644 --- a/app/Models/Payroll.php +++ b/app/Models/Payroll.php @@ -54,13 +54,13 @@ public function forCurrentUser(Builder $query): void } } - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - public function adjustments(): HasMany { return $this->hasMany(PayrollAdjustment::class); } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } } diff --git a/app/Models/Perfume.php b/app/Models/Perfume.php index 1df2d0e..dfcd3df 100644 --- a/app/Models/Perfume.php +++ b/app/Models/Perfume.php @@ -48,28 +48,28 @@ public function categories(): BelongsToMany return $this->belongsToMany(Category::class); } + public function items(): MorphMany + { + return $this->morphMany(OrderItem::class, 'orderable'); + } + public function outlets(): BelongsToMany { return $this->belongsToMany(Outlet::class)->withPivot('stock'); } - public function warehouses(): BelongsToMany - { - return $this->belongsToMany(Warehouse::class)->withPivot('stock'); - } - public function purchaseItems(): MorphMany { return $this->morphMany(PurchaseItem::class, 'purchasable'); } - public function items(): MorphMany - { - return $this->morphMany(OrderItem::class, 'orderable'); - } - public function restockItems(): MorphMany { return $this->morphMany(RestockItem::class, 'restockable'); } + + public function warehouses(): BelongsToMany + { + return $this->belongsToMany(Warehouse::class)->withPivot('stock'); + } } diff --git a/app/Models/Product.php b/app/Models/Product.php index 3f4f55f..492b162 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -35,28 +35,28 @@ public function getSlugOptions(): SlugOptions ->saveSlugsTo('slug'); } + public function items(): MorphMany + { + return $this->morphMany(OrderItem::class, 'orderable'); + } + public function outlets(): BelongsToMany { return $this->belongsToMany(Outlet::class)->withPivot('stock'); } - public function warehouses(): BelongsToMany - { - return $this->belongsToMany(Warehouse::class)->withPivot('stock'); - } - public function purchaseItems(): MorphMany { return $this->morphMany(PurchaseItem::class, 'purchasable'); } - public function items(): MorphMany - { - return $this->morphMany(OrderItem::class, 'orderable'); - } - public function restockItems(): MorphMany { return $this->morphMany(RestockItem::class, 'restockable'); } + + public function warehouses(): BelongsToMany + { + return $this->belongsToMany(Warehouse::class)->withPivot('stock'); + } } diff --git a/app/Models/Tier.php b/app/Models/Tier.php index f53ab53..100903f 100644 --- a/app/Models/Tier.php +++ b/app/Models/Tier.php @@ -39,21 +39,6 @@ public static function isOverlap(int $minSpending, ?int $maxSpending, ?int $igno }); } - public function memberships(): HasMany - { - return $this->hasMany(Membership::class); - } - - public function vouchers(): BelongsToMany - { - return $this->belongsToMany(Voucher::class); - } - - public function rewards(): BelongsToMany - { - return $this->belongsToMany(Reward::class); - } - public static function getHighestTier() { return self::orderByDesc('min_spending')->first(); @@ -142,4 +127,19 @@ public static function canDeleteTier(int $tierId): array 'message' => 'Tidak dapat menghapus tier tengah karena akan mengacaukan sistem belanja. Tier harus membentuk rantai yang kontinyu.', ]; } + + public function memberships(): HasMany + { + return $this->hasMany(Membership::class); + } + + public function rewards(): BelongsToMany + { + return $this->belongsToMany(Reward::class); + } + + public function vouchers(): BelongsToMany + { + return $this->belongsToMany(Voucher::class); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 321478e..709e644 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -57,44 +57,6 @@ protected function withoutDeveloper(Builder $query): void }); } - // HasOne relationships (alphabetical) - public function customer(): HasOne - { - return $this->hasOne(Customer::class); - } - - public function employee(): HasOne - { - return $this->hasOne(Employee::class); - } - - public function membership(): HasOne - { - return $this->hasOne(Membership::class); - } - - public function referralCode(): HasOne - { - return $this->hasOne(ReferralCode::class); - } - - public function referralUsage(): HasOne - { - return $this->hasOne(ReferralUsage::class); - } - - // BelongsToMany relationships (alphabetical) - public function outlets(): BelongsToMany - { - return $this->belongsToMany(Outlet::class); - } - - public function vouchers(): BelongsToMany - { - return $this->belongsToMany(Voucher::class); - } - - // HasMany relationships (alphabetical) public function articles(): HasMany { return $this->hasMany(Article::class, 'author_id'); @@ -105,6 +67,16 @@ public function broadcasts(): HasMany return $this->hasMany(Broadcast::class); } + public function customer(): HasOne + { + return $this->hasOne(Customer::class); + } + + public function employee(): HasOne + { + return $this->hasOne(Employee::class); + } + public function expenses(): HasMany { return $this->hasMany(Expense::class); @@ -115,11 +87,21 @@ public function items(): HasMany return $this->hasMany(OrderItem::class); } + public function membership(): HasOne + { + return $this->hasOne(Membership::class); + } + public function orders(): HasMany { return $this->hasMany(Order::class); } + public function outlets(): BelongsToMany + { + return $this->belongsToMany(Outlet::class); + } + public function payments(): HasMany { return $this->hasMany(Payment::class); @@ -150,6 +132,16 @@ public function redemptions(): HasMany return $this->hasMany(Redemption::class); } + public function referralCode(): HasOne + { + return $this->hasOne(ReferralCode::class); + } + + public function referralUsage(): HasOne + { + return $this->hasOne(ReferralUsage::class); + } + public function salaryHistories(): HasMany { return $this->hasMany(SalaryHistory::class); @@ -164,4 +156,9 @@ public function verifiedRedemptions(): HasMany { return $this->hasMany(Redemption::class, 'verified_by'); } + + public function vouchers(): BelongsToMany + { + return $this->belongsToMany(Voucher::class); + } } diff --git a/app/Models/Warehouse.php b/app/Models/Warehouse.php index b5279af..5a58f01 100644 --- a/app/Models/Warehouse.php +++ b/app/Models/Warehouse.php @@ -18,6 +18,11 @@ class Warehouse extends Model protected $cascadeDeletes = ['perfumes', 'products', 'bottles', 'purchases', 'restocks']; + public function bottles(): BelongsToMany + { + return $this->belongsToMany(Bottle::class)->withPivot('stock'); + } + public function perfumes(): BelongsToMany { return $this->belongsToMany(Perfume::class)->withPivot('stock'); @@ -28,11 +33,6 @@ public function products(): BelongsToMany return $this->belongsToMany(Product::class)->withPivot('stock'); } - public function bottles(): BelongsToMany - { - return $this->belongsToMany(Bottle::class)->withPivot('stock'); - } - public function purchases(): HasMany { return $this->hasMany(Purchase::class); diff --git a/database/factories/BottleFactory.php b/database/factories/BottleFactory.php index e455bbb..a43c16a 100644 --- a/database/factories/BottleFactory.php +++ b/database/factories/BottleFactory.php @@ -11,15 +11,17 @@ class BottleFactory extends Factory public function definition(): array { - $costPrice = $this->faker->randomNumber(2); - return [ 'name' => $this->faker->word(), 'slug' => $this->faker->slug(), 'size' => $this->faker->randomNumber(2), 'cost_price' => $this->faker->randomNumber(2), 'sale_price' => $this->faker->randomNumber(2), - 'description' => $this->faker->sentence(), ]; } + + public function withDescription(): Factory + { + return $this->state(fn () => ['description' => $this->faker->sentence()]); + } } diff --git a/database/factories/CategoryFactory.php b/database/factories/CategoryFactory.php index ca513dc..d30992d 100644 --- a/database/factories/CategoryFactory.php +++ b/database/factories/CategoryFactory.php @@ -14,7 +14,6 @@ public function definition(): array return [ 'name' => $this->faker->unique()->company(), 'slug' => $this->faker->slug(), - 'description' => $this->faker->optional()->text(), 'sort_order' => function () { $max = Category::max('sort_order') ?? 0; @@ -22,4 +21,9 @@ public function definition(): array }, ]; } + + public function withDescription(): Factory + { + return $this->state(fn () => ['description' => $this->faker->text()]); + } } diff --git a/database/factories/CustomerFactory.php b/database/factories/CustomerFactory.php index 018a93d..9df2cb3 100644 --- a/database/factories/CustomerFactory.php +++ b/database/factories/CustomerFactory.php @@ -23,6 +23,11 @@ public function definition(): array ]; } + public function withAddress(): Factory + { + return $this->state(fn () => ['address' => $this->faker->address()]); + } + public function male(): Factory { return $this->state(fn () => ['gender' => Gender::MALE]); diff --git a/database/factories/EmployeeFactory.php b/database/factories/EmployeeFactory.php index 9d1d8aa..3f6e73a 100644 --- a/database/factories/EmployeeFactory.php +++ b/database/factories/EmployeeFactory.php @@ -24,15 +24,23 @@ public function definition(): array $this->faker->numerify('0812 #### #####'), ]), 'base_salary' => $this->faker->randomNumber(6), - 'address' => $this->faker->address(), 'birthdate' => $this->faker->dateTimeBetween('-60 years', '-18 years')->format('Y-m-d'), 'hire_date' => $this->faker->date(), - 'resign_date' => $this->faker->optional()->date(), 'gender' => $this->faker->randomElement(Gender::cases()), 'status' => $this->faker->randomElement(EmploymentStatus::cases()), ]; } + public function withAddress(): Factory + { + return $this->state(fn () => ['address' => $this->faker->address()]); + } + + public function resigned(): Factory + { + return $this->state(fn () => ['resign_date' => $this->faker->date()]); + } + public function male(): Factory { return $this->state(fn () => ['gender' => Gender::MALE]); diff --git a/database/factories/MembershipFactory.php b/database/factories/MembershipFactory.php index 7ecba95..f717adf 100644 --- a/database/factories/MembershipFactory.php +++ b/database/factories/MembershipFactory.php @@ -5,6 +5,7 @@ use App\Models\Membership; use App\Models\Tier; use App\Models\User; +use DateTime; use Illuminate\Database\Eloquent\Factories\Factory; class MembershipFactory extends Factory @@ -18,7 +19,11 @@ public function definition(): array 'tier_id' => Tier::factory(), 'total_spending' => $this->faker->numberBetween(0, 5000000), 'reward_points' => $this->faker->numberBetween(0, 1000), - 'last_transaction_at' => $this->faker->optional()->dateTimeBetween('-30 days', 'now'), ]; } + + public function withLastTransaction(?DateTime $date = null): Factory + { + return $this->state(fn () => ['last_transaction_at' => $date ?? now()]); + } } diff --git a/database/factories/OrderItemFactory.php b/database/factories/OrderItemFactory.php index 362398a..b1d6ac0 100644 --- a/database/factories/OrderItemFactory.php +++ b/database/factories/OrderItemFactory.php @@ -2,8 +2,11 @@ namespace Database\Factories; +use App\Models\Bottle; use App\Models\Order; use App\Models\OrderItem; +use App\Models\Perfume; +use App\Models\Product; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; @@ -17,13 +20,45 @@ public function definition(): array $quantity = $this->faker->numberBetween(1, 5); return [ - 'order_id' => Order::factory(), 'user_id' => User::factory(), - 'orderable_type' => null, - 'orderable_id' => null, 'cogs' => (int) ($unitPrice * 0.4), 'unit_price' => $unitPrice, 'quantity' => $quantity, ]; } + + public function forOrder(?Order $order = null): Factory + { + return $this->state(fn () => ['order_id' => $order?->id ?? Order::factory()]); + } + + public function forProduct(?Product $product = null): Factory + { + $product = $product ?? Product::factory()->create(); + + return $this->state(fn () => [ + 'orderable_type' => $product->getMorphClass(), + 'orderable_id' => $product->id, + ]); + } + + public function forPerfume(?Perfume $perfume = null): Factory + { + $perfume = $perfume ?? Perfume::factory()->create(); + + return $this->state(fn () => [ + 'orderable_type' => $perfume->getMorphClass(), + 'orderable_id' => $perfume->id, + ]); + } + + public function forBottle(?Bottle $bottle = null): Factory + { + $bottle = $bottle ?? Bottle::factory()->create(); + + return $this->state(fn () => [ + 'orderable_type' => $bottle->getMorphClass(), + 'orderable_id' => $bottle->id, + ]); + } } diff --git a/database/factories/OutletFactory.php b/database/factories/OutletFactory.php index d069d8c..f7a7012 100644 --- a/database/factories/OutletFactory.php +++ b/database/factories/OutletFactory.php @@ -21,13 +21,26 @@ public function definition(): array $this->faker->numerify('0812 #### #####'), ]), 'address' => $this->faker->address(), - 'landmark' => $this->faker->streetName(), - 'maps_url' => 'https://goo.gl/maps/'.$this->faker->regexify('[A-Za-z0-9]{6,10}'), 'opened_date' => $this->faker->date(), 'status' => $this->faker->randomElement(OutletStatus::cases()), ]; } + public function withLandmark(): Factory + { + return $this->state(fn () => ['landmark' => $this->faker->streetName()]); + } + + public function withMapsUrl(): Factory + { + return $this->state(fn () => ['maps_url' => 'https://goo.gl/maps/'.$this->faker->regexify('[A-Za-z0-9]{6,10}')]); + } + + public function closed(): Factory + { + return $this->state(fn () => ['closed_date' => $this->faker->date()]); + } + public function operational(): Factory { return $this->state(fn () => ['status' => OutletStatus::OPERATIONAL]); diff --git a/database/factories/PaymentFactory.php b/database/factories/PaymentFactory.php index ca231a4..b3683fb 100644 --- a/database/factories/PaymentFactory.php +++ b/database/factories/PaymentFactory.php @@ -7,6 +7,7 @@ use App\Models\Order; use App\Models\Payment; use App\Models\User; +use DateTime; use Illuminate\Database\Eloquent\Factories\Factory; class PaymentFactory extends Factory @@ -20,11 +21,19 @@ public function definition(): array 'user_id' => User::factory(), 'method' => $this->faker->randomElement(PaymentMethod::cases()), 'amount' => $this->faker->numberBetween(20000, 400000), - 'paid_at' => $this->faker->dateTimeBetween('-7 days', 'now'), - 'status' => $this->faker->randomElement(PaymentStatus::cases()), ]; } + public function withPaidAt(?DateTime $paidAt = null): Factory + { + return $this->state(fn () => ['paid_at' => $paidAt ?? now()]); + } + + public function withStatus(PaymentStatus $status): Factory + { + return $this->state(fn () => ['status' => $status]); + } + public function cash(): Factory { return $this->state(fn () => ['method' => PaymentMethod::CASH]); diff --git a/database/factories/PayrollFactory.php b/database/factories/PayrollFactory.php index ca8db12..69f8ae2 100644 --- a/database/factories/PayrollFactory.php +++ b/database/factories/PayrollFactory.php @@ -5,6 +5,7 @@ use App\Enums\IsPaid; use App\Models\Payroll; use App\Models\User; +use DateTime; use Illuminate\Database\Eloquent\Factories\Factory; class PayrollFactory extends Factory @@ -17,8 +18,6 @@ public function definition(): array $bonus = $this->faker->numberBetween(0, 2000000); $deduction = $this->faker->numberBetween(0, 500000); - $isPaid = $this->faker->randomElement(IsPaid::cases()); - return [ 'user_id' => User::factory(), 'period_month' => $this->faker->date('Y-m'), @@ -26,13 +25,23 @@ public function definition(): array 'bonus' => $bonus, 'deduction' => $deduction, 'total_salary' => $base + $bonus - $deduction, - 'is_paid' => $isPaid, - 'paid_at' => $isPaid === IsPaid::PAID - ? $this->faker->dateTimeBetween('-3 days', 'now') - : null, + 'is_paid' => IsPaid::NOT_PAID, ]; } + public function withPaidAt(?DateTime $paidAt = null): Factory + { + return $this->state(fn () => [ + 'is_paid' => IsPaid::PAID, + 'paid_at' => $paidAt ?? now(), + ]); + } + + public function withPeriodMonth(string $periodMonth): Factory + { + return $this->state(fn () => ['period_month' => $periodMonth]); + } + public function paid(): Factory { return $this->state(fn () => [ diff --git a/database/factories/PerfumeFactory.php b/database/factories/PerfumeFactory.php index f6144da..a5ef57f 100644 --- a/database/factories/PerfumeFactory.php +++ b/database/factories/PerfumeFactory.php @@ -24,13 +24,28 @@ public function definition(): array 'concentration' => $this->faker->randomElement(Concentration::cases()), 'cost_price' => $this->faker->numberBetween(50000, 250000), 'sale_price' => $this->faker->numberBetween(80000, 400000), - 'base_notes' => $this->faker->optional()->words(3, true), - 'middle_notes' => $this->faker->optional()->words(3, true), - 'top_notes' => $this->faker->optional()->words(3, true), - 'description' => $this->faker->optional()->paragraph(), ]; } + public function withNotes(): Factory + { + return $this->state(fn () => [ + 'base_notes' => $this->faker->words(3, true), + 'middle_notes' => $this->faker->words(3, true), + 'top_notes' => $this->faker->words(3, true), + ]); + } + + public function withDescription(): Factory + { + return $this->state(fn () => ['description' => $this->faker->paragraph()]); + } + + public function withoutBrand(): Factory + { + return $this->state(fn () => ['brand_id' => null]); + } + public function extrait(): Factory { return $this->state(fn () => ['concentration' => Concentration::EXTRAIT_DE_PERFUME]); diff --git a/database/factories/ProductFactory.php b/database/factories/ProductFactory.php index 01c168d..e4a5527 100644 --- a/database/factories/ProductFactory.php +++ b/database/factories/ProductFactory.php @@ -20,7 +20,11 @@ public function definition(): array 'sku' => strtoupper($this->faker->unique()->bothify('PRD-#####')), 'cost_price' => $this->faker->numberBetween(20000, 150000), 'sale_price' => $this->faker->numberBetween(30000, 200000), - 'description' => $this->faker->optional()->paragraph(), ]; } + + public function withDescription(): Factory + { + return $this->state(fn () => ['description' => $this->faker->paragraph()]); + } } diff --git a/database/factories/RedemptionFactory.php b/database/factories/RedemptionFactory.php new file mode 100644 index 0000000..5718c44 --- /dev/null +++ b/database/factories/RedemptionFactory.php @@ -0,0 +1,43 @@ + User::factory(), + 'reward_id' => Reward::factory(), + 'code' => Str::upper(Str::random(10)), + 'points_spent' => 100, + 'status' => RedemptionStatus::WAITING_PICKUP, + 'expires_at' => $this->faker->dateTimeBetween('+1 week', '+1 month'), + ]; + } + + public function verified(User $verifier): Factory + { + return $this->state(fn () => [ + 'status' => RedemptionStatus::PICKED_UP, + 'verified_at' => now(), + 'verified_by' => $verifier->id, + ]); + } + + public function expired(): Factory + { + return $this->state(fn () => [ + 'status' => RedemptionStatus::EXPIRED, + ]); + } +} diff --git a/database/factories/RestockFactory.php b/database/factories/RestockFactory.php index 41a3aa4..1a6c8f6 100644 --- a/database/factories/RestockFactory.php +++ b/database/factories/RestockFactory.php @@ -7,18 +7,10 @@ use App\Models\Warehouse; use Illuminate\Database\Eloquent\Factories\Factory; -/** - * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Restock> - */ class RestockFactory extends Factory { protected $model = Restock::class; - /** - * Define the model's default state. - * - * @return array - */ public function definition(): array { return [ diff --git a/database/factories/RestockItemFactory.php b/database/factories/RestockItemFactory.php index 5f334c4..74423f0 100644 --- a/database/factories/RestockItemFactory.php +++ b/database/factories/RestockItemFactory.php @@ -10,18 +10,10 @@ use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; -/** - * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\RestockItem> - */ class RestockItemFactory extends Factory { protected $model = RestockItem::class; - /** - * Define the model's default state. - * - * @return array - */ public function definition(): array { $restockableType = fake()->randomElement([Perfume::class, Product::class, Bottle::class]); diff --git a/database/factories/RewardFactory.php b/database/factories/RewardFactory.php new file mode 100644 index 0000000..fa08eeb --- /dev/null +++ b/database/factories/RewardFactory.php @@ -0,0 +1,44 @@ + $this->faker->words(2, true), + 'points' => $this->faker->numberBetween(10, 500), + 'category' => RewardCategory::REGULAR, + 'is_show' => IsShow::SHOW, + 'start_date' => $this->faker->dateTimeBetween('now', '+1 week')->format('Y-m-d'), + ]; + } + + public function withStock(int $stock): Factory + { + return $this->state(fn () => ['stock' => $stock]); + } + + public function withEndDate(string $date): Factory + { + return $this->state(fn () => ['end_date' => $date]); + } + + public function show(): Factory + { + return $this->state(fn () => ['is_show' => IsShow::SHOW]); + } + + public function hidden(): Factory + { + return $this->state(fn () => ['is_show' => IsShow::HIDDEN]); + } +} diff --git a/database/factories/TierFactory.php b/database/factories/TierFactory.php index e2a4a6c..690de89 100644 --- a/database/factories/TierFactory.php +++ b/database/factories/TierFactory.php @@ -14,7 +14,11 @@ public function definition(): array return [ 'name' => $this->faker->name(), 'min_spending' => $this->faker->randomNumber(), - 'max_spending' => $this->faker->optional()->randomNumber(), ]; } + + public function withMaxSpending(int $amount): Factory + { + return $this->state(fn () => ['max_spending' => $amount]); + } } diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 63d8bc2..332b387 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -30,4 +30,9 @@ public function inactive(): Factory { return $this->state(fn () => ['status' => UserStatus::INACTIVE]); } + + public function verified(): Factory + { + return $this->state(fn () => ['email_verified_at' => now()]); + } } diff --git a/database/factories/VoucherFactory.php b/database/factories/VoucherFactory.php index b7f8777..96e4fa6 100644 --- a/database/factories/VoucherFactory.php +++ b/database/factories/VoucherFactory.php @@ -21,20 +21,39 @@ public function definition(): array 'code' => Str::upper(Str::random(8)), 'tags' => $this->faker->words(1, true), 'type' => $type, - 'discount_amount' => $type === VoucherType::PERCENTAGE->value + 'discount_amount' => $type === VoucherType::PERCENTAGE ? $this->faker->numberBetween(5, 50) : $this->faker->numberBetween(10000, 100000), - 'min_purchase' => $this->faker->optional()->numberBetween(50000, 200000), - 'max_discount' => $type === VoucherType::PERCENTAGE->value ? $this->faker->numberBetween(10000, 50000) : null, - 'quota' => $this->faker->optional()->numberBetween(50, 500), - 'limit_per_user' => $this->faker->optional()->numberBetween(1, 5), + 'limit_per_user' => 1, 'summary' => $this->faker->sentence(), 'start_date' => $this->faker->dateTimeBetween('tomorrow', '+1 week')->format('Y-m-d'), - 'end_date' => $this->faker->optional()->dateTimeBetween('+1 day', '+2 months')?->format('Y-m-d'), - 'is_show' => $this->faker->randomElement(IsShow::values()), + 'is_show' => IsShow::SHOW, ]; } + public function withMinPurchase(int $amount): Factory + { + return $this->state(fn () => ['min_purchase' => $amount]); + } + + public function withMaxDiscount(int $amount): Factory + { + return $this->state(fn () => ['max_discount' => $amount]); + } + + public function withQuota(int $quota): Factory + { + return $this->state(fn () => [ + 'quota' => $quota, + 'available_count' => $quota, + ]); + } + + public function withEndDate(string $date): Factory + { + return $this->state(fn () => ['end_date' => $date]); + } + public function show(): Factory { return $this->state(fn () => ['is_show' => IsShow::SHOW]); diff --git a/database/factories/WarehouseFactory.php b/database/factories/WarehouseFactory.php index d40e59b..2998270 100644 --- a/database/factories/WarehouseFactory.php +++ b/database/factories/WarehouseFactory.php @@ -5,23 +5,19 @@ use App\Models\Warehouse; use Illuminate\Database\Eloquent\Factories\Factory; -/** - * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Warehouse> - */ class WarehouseFactory extends Factory { protected $model = Warehouse::class; - /** - * Define the model's default state. - * - * @return array - */ public function definition(): array { return [ 'name' => fake()->company().' Warehouse', - 'description' => fake()->optional()->sentence(), ]; } + + public function withDescription(): Factory + { + return $this->state(fn () => ['description' => fake()->sentence()]); + } } diff --git a/database/seeders/CategorySeeder.php b/database/seeders/CategorySeeder.php index b2f9b78..31ae28b 100644 --- a/database/seeders/CategorySeeder.php +++ b/database/seeders/CategorySeeder.php @@ -2,6 +2,7 @@ namespace Database\Seeders; +use App\Enums\CategoryType; use App\Models\Category; use Illuminate\Database\Seeder; use Illuminate\Support\Str; @@ -11,16 +12,20 @@ class CategorySeeder extends Seeder public function run(): void { $categories = [ - ['name' => 'Floral', 'description' => 'Perfume with floral scents', 'sort_order' => 1], - ['name' => 'Citrus', 'description' => 'Fresh citrus fragrances', 'sort_order' => 2], - ['name' => 'Woody', 'description' => 'Warm woody notes', 'sort_order' => 3], - ['name' => 'Oriental', 'description' => 'Spicy and exotic oriental scents', 'sort_order' => 4], - ['name' => 'Fruity', 'description' => 'Sweet and fresh fruity fragrances', 'sort_order' => 5], - ['name' => 'Aquatic', 'description' => 'Light, oceanic, and fresh scents', 'sort_order' => 6], - ['name' => 'Gourmand', 'description' => 'Edible, sweet and dessert-like scents', 'sort_order' => 7], - ['name' => 'Green', 'description' => 'Fresh, leafy, and herbal notes', 'sort_order' => 8], - ['name' => 'Chypre', 'description' => 'Classic blend of citrus, oakmoss, and labdanum', 'sort_order' => 9], - ['name' => 'Musk', 'description' => 'Warm, sensual musky fragrances', 'sort_order' => 10], + ['name' => 'Floral', 'description' => 'Perfume with floral scents', 'sort_order' => 1, 'type' => CategoryType::PERFUME], + ['name' => 'Citrus', 'description' => 'Fresh citrus fragrances', 'sort_order' => 2, 'type' => CategoryType::PERFUME], + ['name' => 'Woody', 'description' => 'Warm woody notes', 'sort_order' => 3, 'type' => CategoryType::PERFUME], + ['name' => 'Oriental', 'description' => 'Spicy and exotic oriental scents', 'sort_order' => 4, 'type' => CategoryType::PERFUME], + ['name' => 'Fruity', 'description' => 'Sweet and fresh fruity fragrances', 'sort_order' => 5, 'type' => CategoryType::PERFUME], + ['name' => 'Aquatic', 'description' => 'Light, oceanic, and fresh scents', 'sort_order' => 6, 'type' => CategoryType::PERFUME], + ['name' => 'Gourmand', 'description' => 'Edible, sweet and dessert-like scents', 'sort_order' => 7, 'type' => CategoryType::PERFUME], + ['name' => 'Green', 'description' => 'Fresh, leafy, and herbal notes', 'sort_order' => 8, 'type' => CategoryType::PERFUME], + ['name' => 'Chypre', 'description' => 'Classic blend of citrus, oakmoss, and labdanum', 'sort_order' => 9, 'type' => CategoryType::PERFUME], + ['name' => 'Musk', 'description' => 'Warm, sensual musky fragrances', 'sort_order' => 10, 'type' => CategoryType::PERFUME], + ['name' => 'Tips n Tricks', 'description' => 'Tips and tricks for using perfumes', 'sort_order' => 11, 'type' => CategoryType::ARTICLE], + ['name' => 'Perfume Review', 'description' => 'Reviews of perfumes', 'sort_order' => 12, 'type' => CategoryType::ARTICLE], + ['name' => 'Perfume Set', 'description' => 'Perfume sets', 'sort_order' => 13, 'type' => CategoryType::ARTICLE], + ['name' => 'How To', 'description' => 'How to use perfumes', 'sort_order' => 14, 'type' => CategoryType::ARTICLE], ]; foreach ($categories as $category) { @@ -29,6 +34,7 @@ public function run(): void 'slug' => Str::slug($category['name']), 'description' => $category['description'], 'sort_order' => $category['sort_order'], + 'type' => $category['type'], ]); } } diff --git a/database/seeders/OutletSeeder.php b/database/seeders/OutletSeeder.php index d110232..5036f22 100644 --- a/database/seeders/OutletSeeder.php +++ b/database/seeders/OutletSeeder.php @@ -46,8 +46,10 @@ public function run(): void foreach ($outlets as $data) { $outlet = Outlet::create([ ...$data, + 'maps_url' => null, 'status' => OutletStatus::OPERATIONAL, 'opened_date' => now()->toDateString(), + 'closed_date' => null, ]); $hours = collect(Day::cases())->map(fn ($day) => [ diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php index f228937..5c85e10 100644 --- a/database/seeders/UserSeeder.php +++ b/database/seeders/UserSeeder.php @@ -5,6 +5,7 @@ use App\Enums\EmploymentStatus; use App\Enums\Gender; use App\Enums\UserRole; +use App\Enums\UserStatus; use App\Models\Employee; use App\Models\Outlet; use App\Models\ReferralCode; @@ -23,6 +24,8 @@ public function run(): void 'email' => 'project.pangestuyoga@gmail.com', 'username' => 'pangestu', 'password' => Hash::make(config('myconfig.password_default')), + 'status' => UserStatus::ACTIVE, + 'email_verified_at' => now(), ]); Employee::create([ @@ -50,6 +53,8 @@ public function run(): void 'email' => 'mulyadi@gmail.com', 'username' => 'mulyadi', 'password' => Hash::make(config('myconfig.password_default')), + 'status' => UserStatus::ACTIVE, + 'email_verified_at' => now(), ]); Employee::create([