refactor: standardize model structure, normalize bidirectional relations, and update documentation.
This commit is contained in:
parent
8ce493ebf2
commit
0986ec780b
@ -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
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user