refactor: standardize model structure, normalize bidirectional relations, and update documentation.

This commit is contained in:
Yoga Pangestu 2026-06-22 11:26:47 +07:00
parent 8ce493ebf2
commit 0986ec780b
14 changed files with 233 additions and 76 deletions

View File

@ -14,6 +14,95 @@ ### Traits
use FlashesEntityMessage, ParsesDataTableQuery;
```
### Model Eloquent
- **Urutan Penulisan di dalam Model Class:**
Setiap model harus disusun mengikuti urutan struktur berikut dari atas ke bawah:
1. **Use Trait** (misal: `use HasFactory, SoftDeletes;`).
2. **Casting** (metode `casts()`).
3. **Relasi (Relationships)** (diurutkan secara alfabetis berdasarkan nama method relasi).
4. **Attribute (Accessors / Appends)** (misal format harga, format tanggal, dll. Harus dimasukkan dalam array `$appends` di atas class).
5. **Scope** (metode local scope dengan PHP attribute `#[Scope]`).
6. **Method Lainnya** (helper method, logic bisnis, dll.).
- **Hubungan Timbal Balik (2-Way Relations):**
- Pastikan setiap relasi ditulis secara 2 arah (bi-directional).
- Jika suatu model memiliki `belongsTo` ke model lain, pastikan model lain tersebut juga mendefinisikan relasi kebalikannya (`hasMany` atau `hasOne`).
- **Casting & Formatting Attribute:**
- Lakukan casting secara tepat pada kolom yang memerlukan tipe data khusus (seperti Boolean, Integer, Datetime, atau Enum).
- Jika suatu kolom perlu diformat (misalnya konversi harga ke format Rupiah atau format tanggal lokal), buatlah accessor menggunakan class `Attribute` (Eloquent Attribute) dan tambahkan nama atribut tersebut ke properti `$appends` model.
- **Query Scopes:**
- Jika model memiliki opsi/status tertentu, buatlah local scope agar kueri dapat digunakan kembali (reusable). Gunakan PHP attribute `#[Scope]` di atas metode scope.
- **Contoh:**
```php
namespace App\Models;
use App\Enums\OrderStatus;
use Illuminate\Database\Eloquent\Attributes\Appends;
use Illuminate\Database\Eloquent\Attributes\Guarded;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
#[Guarded(['id'])]
#[Appends(['amount_formatted', 'status_label'])]
class Order extends Model
{
// 1. Use Trait
use HasFactory, SoftDeletes;
// 2. Casting
protected function casts(): array
{
return [
'status' => OrderStatus::class,
'amount' => 'integer',
];
}
// 3. Relasi (Urut Abjad)
public function customer(): BelongsTo
{
return $this->belongsTo(Customer::class);
}
public function items(): HasMany
{
return $this->hasMany(OrderItem::class);
}
// 4. Attribute
public function amountFormatted(): Attribute
{
return Attribute::make(
get: fn () => 'Rp '.number_format($this->amount, 0, ',', '.'),
);
}
public function statusLabel(): Attribute
{
return Attribute::make(
get: fn () => $this->status->label(),
);
}
// 5. Scope
#[Scope]
public function pending(Builder $query): void
{
$query->where('status', OrderStatus::PENDING->value);
}
}
```
## Database & Migrasi
### Struktur File Migrasi

View File

@ -12,6 +12,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Spatie\MediaLibrary\HasMedia;
#[Guarded(['id'])]
@ -47,6 +48,11 @@ public function employee(): BelongsTo
return $this->belongsTo(Employee::class);
}
public function payrollAdjustments(): HasMany
{
return $this->hasMany(PayrollAdjustment::class);
}
public function attendanceDateFormatted(): Attribute
{
return Attribute::make(

View File

@ -11,6 +11,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\MediaLibrary\HasMedia;
@ -48,6 +49,11 @@ public function createdBy(): BelongsTo
return $this->belongsTo(User::class, 'created_by_id');
}
public function order(): HasOne
{
return $this->hasOne(Order::class);
}
public function reference(): MorphTo
{
return $this->morphTo();

View File

@ -39,24 +39,24 @@ public function createdBy(): BelongsTo
return $this->belongsTo(User::class, 'created_by_id');
}
public function submittedBy(): BelongsTo
{
return $this->belongsTo(User::class, 'submitted_by_id');
}
public function materials(): HasMany
{
return $this->hasMany(CuttingMaterial::class);
}
public function resultPrices(): HasMany
{
return $this->hasMany(CuttingResultPrice::class);
}
public function results(): HasMany
{
return $this->hasMany(CuttingResult::class);
}
public function resultPrices(): HasMany
public function submittedBy(): BelongsTo
{
return $this->hasMany(CuttingResultPrice::class);
return $this->belongsTo(User::class, 'submitted_by_id');
}
public function createdAtFormatted(): Attribute

View File

@ -31,11 +31,6 @@ protected function casts(): array
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function cutting(): BelongsTo
{
return $this->belongsTo(Cutting::class);
@ -46,6 +41,11 @@ public function rawMaterialPrice(): BelongsTo
return $this->belongsTo(RawMaterialPrice::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function materialUsageFormatted(): Attribute
{
return Attribute::make(

View File

@ -23,11 +23,6 @@ protected function casts(): array
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function cutting(): BelongsTo
{
return $this->belongsTo(Cutting::class);
@ -37,4 +32,9 @@ public function productVariant(): BelongsTo
{
return $this->belongsTo(ProductVariant::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

View File

@ -32,30 +32,6 @@ protected function casts(): array
];
}
#[Scope]
public function contract(Builder $query): void
{
$query->where('employment_status', EmploymentStatus::CONTRACT->value);
}
#[Scope]
public function fullTime(Builder $query): void
{
$query->where('employment_status', EmploymentStatus::FULL_TIME->value);
}
#[Scope]
public function partTime(Builder $query): void
{
$query->where('employment_status', EmploymentStatus::PART_TIME->value);
}
#[Scope]
public function temporary(Builder $query): void
{
$query->where('employment_status', EmploymentStatus::TEMPORARY->value);
}
public function advances(): HasMany
{
return $this->hasMany(EmployeeAdvance::class);
@ -115,4 +91,28 @@ public function resignDateFormatted(): Attribute
get: fn () => $this->resign_date ? Carbon::parse($this->resign_date)->translatedFormat('l, d F Y') : null,
);
}
#[Scope]
public function contract(Builder $query): void
{
$query->where('employment_status', EmploymentStatus::CONTRACT->value);
}
#[Scope]
public function fullTime(Builder $query): void
{
$query->where('employment_status', EmploymentStatus::FULL_TIME->value);
}
#[Scope]
public function partTime(Builder $query): void
{
$query->where('employment_status', EmploymentStatus::PART_TIME->value);
}
#[Scope]
public function temporary(Builder $query): void
{
$query->where('employment_status', EmploymentStatus::TEMPORARY->value);
}
}

View File

@ -58,11 +58,6 @@ public function createdBy(): BelongsTo
return $this->belongsTo(User::class, 'created_by_id');
}
public function marketing(): BelongsTo
{
return $this->belongsTo(User::class, 'marketing_id');
}
public function customer(): BelongsTo
{
return $this->belongsTo(Customer::class);
@ -73,6 +68,11 @@ public function items(): HasMany
return $this->hasMany(OrderItem::class);
}
public function marketing(): BelongsTo
{
return $this->belongsTo(User::class, 'marketing_id');
}
public function channelLabel(): Attribute
{
return Attribute::make(

View File

@ -33,11 +33,6 @@ protected function casts(): array
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function order(): BelongsTo
{
return $this->belongsTo(Order::class);
@ -48,6 +43,11 @@ public function productVariant(): BelongsTo
return $this->belongsTo(ProductVariant::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function quantityFormatted(): Attribute
{
return Attribute::make(

View File

@ -28,6 +28,11 @@ protected function casts(): array
];
}
public function attendance(): BelongsTo
{
return $this->belongsTo(Attendance::class);
}
public function createdBy(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by_id');
@ -38,11 +43,6 @@ public function payroll(): BelongsTo
return $this->belongsTo(Payroll::class);
}
public function attendance(): BelongsTo
{
return $this->belongsTo(Attendance::class);
}
public function amountFormatted(): Attribute
{
return Attribute::make(

View File

@ -12,12 +12,13 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
#[Guarded(['id'])]
#[Appends(['period_label', 'status_label', 'closed_at_formatted'])]
class PayrollPeriod extends Model
{
use HasFactory, InteractsWithActivityLog;
use HasFactory, InteractsWithActivityLog, SoftDeletes;
protected function casts(): array
{

View File

@ -27,6 +27,11 @@ protected function casts(): array
];
}
public function cuttingResultPrices(): HasMany
{
return $this->hasMany(CuttingResultPrice::class);
}
public function cuttingResults(): HasMany
{
return $this->hasMany(CuttingResult::class);
@ -37,11 +42,6 @@ public function orderItems(): HasMany
return $this->hasMany(OrderItem::class);
}
public function cuttingResultPrices(): HasMany
{
return $this->hasMany(CuttingResultPrice::class);
}
public function prices(): HasMany
{
return $this->hasMany(ProductPrice::class, 'variant_id');

View File

@ -33,6 +33,11 @@ public function cuttingMaterials(): HasMany
return $this->hasMany(CuttingMaterial::class);
}
public function purchaseItems(): HasMany
{
return $this->hasMany(PurchaseItem::class);
}
public function rawMaterial(): BelongsTo
{
return $this->belongsTo(RawMaterial::class);

View File

@ -35,23 +35,36 @@ protected function casts(): array
];
}
#[Scope]
public function active(Builder $query): void
{
$query->where('is_active', true);
}
#[Scope]
public function inactive(Builder $query): void
{
$query->where('is_active', false);
}
public function cashTransactions(): HasMany
{
return $this->hasMany(CashTransaction::class, 'created_by_id');
}
public function closedPayrollPeriods(): HasMany
{
return $this->hasMany(PayrollPeriod::class, 'closed_by_id');
}
public function createdCuttings(): HasMany
{
return $this->hasMany(Cutting::class, 'created_by_id');
}
public function createdPayrollAdjustments(): HasMany
{
return $this->hasMany(PayrollAdjustment::class, 'created_by_id');
}
public function cuttingMaterials(): HasMany
{
return $this->hasMany(CuttingMaterial::class);
}
public function cuttingResults(): HasMany
{
return $this->hasMany(CuttingResult::class);
}
public function employee(): HasOne
{
return $this->hasOne(Employee::class);
@ -62,24 +75,49 @@ public function expenses(): HasMany
return $this->hasMany(Expense::class, 'created_by_id');
}
public function marketingOrders(): HasMany
{
return $this->hasMany(Order::class, 'marketing_id');
}
public function orderItems(): HasMany
{
return $this->hasMany(OrderItem::class);
}
public function orders(): HasMany
{
return $this->hasMany(Order::class, 'created_by_id');
}
public function paidPayrolls(): HasMany
{
return $this->hasMany(Payroll::class, 'paid_by_id');
}
public function profile(): HasOne
{
return $this->hasOne(UserProfile::class);
}
public function purchases(): HasMany
{
return $this->hasMany(Purchase::class, 'created_by_id');
}
public function pushSubscriptions(): HasMany
{
return $this->hasMany(PushSubscription::class);
}
public function purchases(): HasMany
public function submittedCuttings(): HasMany
{
return $this->hasMany(Purchase::class, 'created_by_id');
return $this->hasMany(Cutting::class, 'submitted_by_id');
}
public function verifiedLeaveRequests(): HasMany
{
return $this->hasMany(LeaveRequest::class, 'verified_by_id');
}
public function roleLabel(): Attribute
@ -98,4 +136,16 @@ public function roleLabel(): Attribute
},
);
}
#[Scope]
public function active(Builder $query): void
{
$query->where('is_active', true);
}
#[Scope]
public function inactive(Builder $query): void
{
$query->where('is_active', false);
}
}