refactor: reorganize model structure and enhance attribute handling across multiple models, including improved casting, scope definitions, and attribute formatting
This commit is contained in:
parent
b43835335a
commit
393c36e932
104
BEST_PRACTICE.md
104
BEST_PRACTICE.md
@ -20,23 +20,77 @@ ### Model Eloquent
|
|||||||
Setiap model harus disusun mengikuti urutan struktur berikut dari atas ke bawah:
|
Setiap model harus disusun mengikuti urutan struktur berikut dari atas ke bawah:
|
||||||
1. **Use Trait** (misal: `use HasFactory, SoftDeletes;`).
|
1. **Use Trait** (misal: `use HasFactory, SoftDeletes;`).
|
||||||
2. **Casting** (metode `casts()`).
|
2. **Casting** (metode `casts()`).
|
||||||
3. **Relasi (Relationships)** (diurutkan secara alfabetis berdasarkan nama method relasi).
|
3. **Scope** (metode local scope dengan PHP attribute `#[Scope]`).
|
||||||
4. **Attribute (Accessors / Appends)** (misal format harga, format tanggal, dll. Harus dimasukkan dalam array `$appends` di atas class).
|
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]`).
|
5. **Method Lainnya** (helper method, logic bisnis, dll.).
|
||||||
6. **Method Lainnya** (helper method, logic bisnis, dll.).
|
6. **Relasi (Relationships)** (diurutkan secara alfabetis berdasarkan nama method relasi).
|
||||||
|
|
||||||
|
- **Pengelompokan Scope:**
|
||||||
|
- Scope dikelompokkan berdasarkan kolom yang di-query.
|
||||||
|
- Urutkan berdasarkan nama kolom (abjad), lalu di dalam setiap Column Group, urutkan lagi berdasarkan nama method (abjad).
|
||||||
|
- **Contoh:** Jika ada kolom `is_active` dan `status`:
|
||||||
|
```php
|
||||||
|
// Column group: is_active (first alphabetical)
|
||||||
|
#[Scope]
|
||||||
|
public function active(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('is_active', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
public function inactive(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('is_active', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column group: status (second alphabetical)
|
||||||
|
#[Scope]
|
||||||
|
public function completed(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('status', OrderStatus::COMPLETED);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
public function pending(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('status', OrderStatus::PENDING);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- **Hubungan Timbal Balik (2-Way Relations):**
|
- **Hubungan Timbal Balik (2-Way Relations):**
|
||||||
- Pastikan setiap relasi ditulis secara 2 arah (bi-directional).
|
- 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`).
|
- Jika suatu model memiliki `belongsTo` ke model lain, pastikan model lain tersebut juga mendefinisikan relasi kebalikannya (`hasMany` atau `hasOne`).
|
||||||
|
|
||||||
- **Casting & Formatting Attribute:**
|
- **Casting:**
|
||||||
- Lakukan casting secara tepat pada kolom yang memerlukan tipe data khusus (seperti Boolean, Integer, Datetime, atau Enum).
|
- Lakukan casting secara tepat pada kolom yang memerlukan tipe data khusus.
|
||||||
- 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.
|
- Tipe data yang perlu di-cast: `enum`, `boolean`, `integer`, `decimal`, `date`, `datetime`, `array`, `json`, dll.
|
||||||
|
- **Jangan** cast kolom relasi (foreign key).
|
||||||
|
- **Contoh:**
|
||||||
|
```php
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'status' => OrderStatus::class, // enum
|
||||||
|
'is_active' => 'boolean', // boolean
|
||||||
|
'amount' => 'integer', // integer
|
||||||
|
'price' => 'decimal:2', // decimal
|
||||||
|
'birth_date' => 'date', // date
|
||||||
|
'created_at' => 'datetime', // datetime
|
||||||
|
'settings' => 'array', // array/json
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- **Query Scopes:**
|
- **Scope untuk Kolom Opsi:**
|
||||||
- Jika model memiliki opsi/status tertentu, buatlah local scope agar kueri dapat digunakan kembali (reusable). Gunakan PHP attribute `#[Scope]` di atas metode scope.
|
- Semua kolom yang memiliki opsi/status tertentu (seperti `status`, `is_active`, `type`, dll.) **wajib** memiliki scope.
|
||||||
|
- Gunakan PHP attribute `#[Scope]` di atas metode scope.
|
||||||
|
|
||||||
- **Contoh:**
|
- **Attribute untuk Format:**
|
||||||
|
- Kolom yang akan diformat (misalnya konversi harga ke format Rupiah atau format tanggal lokal) **wajib** menggunakan accessor dengan class `Attribute`.
|
||||||
|
- Nama atribut harus dimasukkan dalam properti `$appends` di atas class.
|
||||||
|
- **Contoh:** `amount` → `amount_formatted`, `status` → `status_label`.
|
||||||
|
|
||||||
|
- **Contoh Lengkap:**
|
||||||
```php
|
```php
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
@ -68,15 +122,17 @@ ### Model Eloquent
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Relasi (Urut Abjad)
|
// 3. Scope (grouped by column, then alphabetical)
|
||||||
public function customer(): BelongsTo
|
#[Scope]
|
||||||
|
public function completed(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Customer::class);
|
$query->where('status', OrderStatus::COMPLETED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function items(): HasMany
|
#[Scope]
|
||||||
|
public function pending(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->hasMany(OrderItem::class);
|
$query->where('status', OrderStatus::PENDING);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Attribute
|
// 4. Attribute
|
||||||
@ -94,11 +150,21 @@ ### Model Eloquent
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Scope
|
// 5. Method Lainnya
|
||||||
#[Scope]
|
public static function mediaModuleName(): string
|
||||||
public function pending(Builder $query): void
|
|
||||||
{
|
{
|
||||||
$query->where('status', OrderStatus::PENDING->value);
|
return 'order';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Relation
|
||||||
|
public function customer(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Customer::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function items(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(OrderItem::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@ -17,42 +17,35 @@
|
|||||||
|
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends([
|
#[Appends([
|
||||||
'check_in_at_formatted',
|
|
||||||
'check_out_at_formatted',
|
|
||||||
'attendance_date_formatted',
|
'attendance_date_formatted',
|
||||||
'work_duration_formatted',
|
'check_in_at_formatted',
|
||||||
'check_in_photo_url',
|
'check_in_photo_url',
|
||||||
|
'check_out_at_formatted',
|
||||||
'check_out_photo_url',
|
'check_out_photo_url',
|
||||||
'employee_name',
|
'employee_name',
|
||||||
|
'work_duration_formatted',
|
||||||
])]
|
])]
|
||||||
class Attendance extends Model implements HasMedia
|
class Attendance extends Model implements HasMedia
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, HasModuleMedia, InteractsWithActivityLog;
|
use HasFactory, HasModuleMedia, InteractsWithActivityLog;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'attendance_date' => 'date',
|
'attendance_date' => 'date',
|
||||||
'check_in_at' => 'datetime',
|
'check_in_at' => 'datetime',
|
||||||
'check_out_at' => 'datetime',
|
|
||||||
'check_in_latitude' => 'decimal:7',
|
'check_in_latitude' => 'decimal:7',
|
||||||
'check_in_longitude' => 'decimal:7',
|
'check_in_longitude' => 'decimal:7',
|
||||||
|
'check_out_at' => 'datetime',
|
||||||
'check_out_latitude' => 'decimal:7',
|
'check_out_latitude' => 'decimal:7',
|
||||||
'check_out_longitude' => 'decimal:7',
|
'check_out_longitude' => 'decimal:7',
|
||||||
'work_duration_minutes' => 'integer',
|
'work_duration_minutes' => 'integer',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function employee(): BelongsTo
|
// 3. Attribute
|
||||||
{
|
|
||||||
return $this->belongsTo(Employee::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function payrollAdjustments(): HasMany
|
|
||||||
{
|
|
||||||
return $this->hasMany(PayrollAdjustment::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function attendanceDateFormatted(): Attribute
|
public function attendanceDateFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -123,6 +116,7 @@ public function workDurationFormatted(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Other Methods
|
||||||
public static function mediaModuleName(): string
|
public static function mediaModuleName(): string
|
||||||
{
|
{
|
||||||
return 'attendance';
|
return 'attendance';
|
||||||
@ -133,4 +127,15 @@ public function registerMediaCollections(): void
|
|||||||
$this->addMediaCollection('checkin')->singleFile();
|
$this->addMediaCollection('checkin')->singleFile();
|
||||||
$this->addMediaCollection('checkout')->singleFile();
|
$this->addMediaCollection('checkout')->singleFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Relation
|
||||||
|
public function employee(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Employee::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function payrollAdjustments(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(PayrollAdjustment::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,8 +14,10 @@
|
|||||||
#[Appends(['balance_formatted'])]
|
#[Appends(['balance_formatted'])]
|
||||||
class CashAccount extends Model
|
class CashAccount extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, InteractsWithActivityLog;
|
use HasFactory, InteractsWithActivityLog;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -23,15 +25,17 @@ protected function casts(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function transactions(): HasMany
|
// 3. Attribute
|
||||||
{
|
|
||||||
return $this->hasMany(CashTransaction::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function balanceFormatted(): Attribute
|
public function balanceFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: fn () => 'Rp '.number_format($this->balance, 0, ',', '.'),
|
get: fn () => 'Rp '.number_format($this->balance, 0, ',', '.'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Relation
|
||||||
|
public function transactions(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(CashTransaction::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,8 @@
|
|||||||
use App\Models\Concerns\InteractsWithActivityLog;
|
use App\Models\Concerns\InteractsWithActivityLog;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Appends;
|
use Illuminate\Database\Eloquent\Attributes\Appends;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
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\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
@ -20,45 +22,42 @@
|
|||||||
#[Appends([
|
#[Appends([
|
||||||
'amount_formatted',
|
'amount_formatted',
|
||||||
'balance_after_formatted',
|
'balance_after_formatted',
|
||||||
'reference_label',
|
|
||||||
'is_incoming',
|
|
||||||
'source_badge_class',
|
|
||||||
'created_at_formatted',
|
'created_at_formatted',
|
||||||
'created_by_name',
|
'created_by_name',
|
||||||
|
'is_incoming',
|
||||||
|
'reference_label',
|
||||||
|
'source_badge_class',
|
||||||
])]
|
])]
|
||||||
class CashTransaction extends Model implements HasMedia
|
class CashTransaction extends Model implements HasMedia
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, HasModuleMedia, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, HasModuleMedia, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'type' => CashTransactionType::class,
|
|
||||||
'amount' => 'integer',
|
'amount' => 'integer',
|
||||||
'balance_after' => 'integer',
|
'balance_after' => 'integer',
|
||||||
|
'type' => CashTransactionType::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cashAccount(): BelongsTo
|
// 3. Scope (grouped by column, then alphabetical)
|
||||||
|
// Column Group: type
|
||||||
|
#[Scope]
|
||||||
|
protected function deposit(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(CashAccount::class);
|
$query->where('type', CashTransactionType::DEPOSIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createdBy(): BelongsTo
|
#[Scope]
|
||||||
|
protected function withdrawal(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'created_by_id');
|
$query->where('type', CashTransactionType::WITHDRAWAL);
|
||||||
}
|
|
||||||
|
|
||||||
public function order(): HasOne
|
|
||||||
{
|
|
||||||
return $this->hasOne(Order::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function reference(): MorphTo
|
|
||||||
{
|
|
||||||
return $this->morphTo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Attribute
|
||||||
public function amountFormatted(): Attribute
|
public function amountFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -96,20 +95,6 @@ public function isIncoming(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sourceBadgeClass(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: fn () => match ($this->reference_type) {
|
|
||||||
null => 'bg-blue-100 text-blue-700 hover:bg-blue-100',
|
|
||||||
Expense::class => 'bg-red-100 text-red-700 hover:bg-red-100',
|
|
||||||
EmployeeAdvance::class => 'bg-amber-100 text-amber-700 hover:bg-amber-100',
|
|
||||||
Payroll::class => 'bg-green-100 text-green-700 hover:bg-green-100',
|
|
||||||
Order::class => 'bg-purple-100 text-purple-700 hover:bg-purple-100',
|
|
||||||
default => 'bg-gray-100 text-gray-700 hover:bg-gray-100',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function referenceLabel(): Attribute
|
public function referenceLabel(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -127,10 +112,24 @@ public function referenceLabel(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isEmployeeAdvanceRepayment(): bool
|
public function sourceBadgeClass(): Attribute
|
||||||
{
|
{
|
||||||
return $this->reference instanceof EmployeeAdvance
|
return Attribute::make(
|
||||||
&& $this->reference->repayment_cash_transaction_id === $this->id;
|
get: fn () => match ($this->reference_type) {
|
||||||
|
null => 'bg-blue-100 text-blue-700 hover:bg-blue-100',
|
||||||
|
Expense::class => 'bg-red-100 text-red-700 hover:bg-red-100',
|
||||||
|
EmployeeAdvance::class => 'bg-amber-100 text-amber-700 hover:bg-amber-100',
|
||||||
|
Payroll::class => 'bg-green-100 text-green-700 hover:bg-green-100',
|
||||||
|
Order::class => 'bg-purple-100 text-purple-700 hover:bg-purple-100',
|
||||||
|
default => 'bg-gray-100 text-gray-700 hover:bg-gray-100',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Other Methods
|
||||||
|
public function getEmployeeAdvanceLabel(): string
|
||||||
|
{
|
||||||
|
return $this->isEmployeeAdvanceRepayment() ? 'Pelunasan Kasbon' : 'Pencairan Kasbon';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getManualTransactionLabel(): string
|
public function getManualTransactionLabel(): string
|
||||||
@ -138,19 +137,15 @@ public function getManualTransactionLabel(): string
|
|||||||
return $this->type === CashTransactionType::WITHDRAWAL ? 'Tarik Kas' : 'Setor Kas';
|
return $this->type === CashTransactionType::WITHDRAWAL ? 'Tarik Kas' : 'Setor Kas';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmployeeAdvanceLabel(): string
|
public function isEmployeeAdvanceRepayment(): bool
|
||||||
{
|
{
|
||||||
return $this->isEmployeeAdvanceRepayment() ? 'Pelunasan Kasbon' : 'Pencairan Kasbon';
|
return $this->reference instanceof EmployeeAdvance
|
||||||
|
&& $this->reference->repayment_cash_transaction_id === $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function mediaModuleName(): string
|
public static function isIncomingTransaction(self $transaction): bool
|
||||||
{
|
{
|
||||||
return 'cash';
|
return (bool) $transaction->is_incoming;
|
||||||
}
|
|
||||||
|
|
||||||
public function registerMediaCollections(): void
|
|
||||||
{
|
|
||||||
$this->addMediaCollection('photos');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function labelForReferenceType(?string $referenceType): string
|
public static function labelForReferenceType(?string $referenceType): string
|
||||||
@ -168,8 +163,34 @@ public static function labelForReferenceType(?string $referenceType): string
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function isIncomingTransaction(self $transaction): bool
|
public static function mediaModuleName(): string
|
||||||
{
|
{
|
||||||
return (bool) $transaction->is_incoming;
|
return 'cash';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerMediaCollections(): void
|
||||||
|
{
|
||||||
|
$this->addMediaCollection('photos');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Relation
|
||||||
|
public function cashAccount(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(CashAccount::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,15 +15,18 @@
|
|||||||
#[Sluggable(from: 'name', to: 'slug')]
|
#[Sluggable(from: 'name', to: 'slug')]
|
||||||
class Category extends Model
|
class Category extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
public function products(): BelongsToMany
|
// 2. Other Methods
|
||||||
{
|
|
||||||
return $this->belongsToMany(Product::class, 'product_categories');
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getActiveWithProducts(): Collection
|
public static function getActiveWithProducts(): Collection
|
||||||
{
|
{
|
||||||
return self::whereHas('products')->get(['id', 'name', 'slug']);
|
return self::whereHas('products')->get(['id', 'name', 'slug']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Relation
|
||||||
|
public function products(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Product::class, 'product_categories');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,10 @@
|
|||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
class Customer extends Model
|
class Customer extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Relation
|
||||||
public function orders(): HasMany
|
public function orders(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(Order::class);
|
return $this->hasMany(Order::class);
|
||||||
|
|||||||
@ -18,24 +18,68 @@
|
|||||||
|
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends([
|
#[Appends([
|
||||||
'status_label',
|
|
||||||
'created_at_formatted',
|
'created_at_formatted',
|
||||||
|
'status_label',
|
||||||
])]
|
])]
|
||||||
class Cutting extends Model
|
class Cutting extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, HasRejection, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, HasRejection, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
'cost_per_unit' => 'integer',
|
||||||
|
'other_cost' => 'integer',
|
||||||
|
'sewing_cost' => 'integer',
|
||||||
'status' => CuttingStatus::class,
|
'status' => CuttingStatus::class,
|
||||||
'total_material_cost' => 'integer',
|
'total_material_cost' => 'integer',
|
||||||
'sewing_cost' => 'integer',
|
|
||||||
'other_cost' => 'integer',
|
|
||||||
'cost_per_unit' => 'integer',
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Scope (grouped by column, then alphabetical)
|
||||||
|
// Column Group: status
|
||||||
|
#[Scope]
|
||||||
|
protected function completed(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('status', CuttingStatus::COMPLETED);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function inProgress(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('status', CuttingStatus::IN_PROGRESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function pendingVerification(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('status', CuttingStatus::PENDING_VERIFICATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function verified(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('status', CuttingStatus::VERIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Attribute
|
||||||
|
public function createdAtFormatted(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => $this->created_at?->translatedFormat('l, d F Y H:i'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function statusLabel(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => $this->status->label(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Relation
|
||||||
public function createdBy(): BelongsTo
|
public function createdBy(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'created_by_id');
|
return $this->belongsTo(User::class, 'created_by_id');
|
||||||
@ -60,42 +104,4 @@ public function submittedBy(): BelongsTo
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'submitted_by_id');
|
return $this->belongsTo(User::class, 'submitted_by_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createdAtFormatted(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: fn () => $this->created_at?->translatedFormat('l, d F Y H:i'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function statusLabel(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: fn () => $this->status->label(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Scope]
|
|
||||||
public function inProgress(Builder $query): void
|
|
||||||
{
|
|
||||||
$query->where('status', CuttingStatus::IN_PROGRESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Scope]
|
|
||||||
public function completed(Builder $query): void
|
|
||||||
{
|
|
||||||
$query->where('status', CuttingStatus::COMPLETED);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Scope]
|
|
||||||
public function pendingVerification(Builder $query): void
|
|
||||||
{
|
|
||||||
$query->where('status', CuttingStatus::PENDING_VERIFICATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Scope]
|
|
||||||
public function verified(Builder $query): void
|
|
||||||
{
|
|
||||||
$query->where('status', CuttingStatus::VERIFIED);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,8 +19,10 @@
|
|||||||
])]
|
])]
|
||||||
class CuttingMaterial extends Model
|
class CuttingMaterial extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -28,21 +30,7 @@ protected function casts(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cutting(): BelongsTo
|
// 3. Attribute
|
||||||
{
|
|
||||||
return $this->belongsTo(Cutting::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function rawMaterialPrice(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(RawMaterialPrice::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function user(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(User::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function materialUsageFormatted(): Attribute
|
public function materialUsageFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -64,6 +52,7 @@ public function unitAbbreviation(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Other Methods
|
||||||
public function materialCost(): int
|
public function materialCost(): int
|
||||||
{
|
{
|
||||||
$price = $this->rawMaterialPrice;
|
$price = $this->rawMaterialPrice;
|
||||||
@ -86,4 +75,20 @@ private function formatQuantityInput(float|string|null $value): string
|
|||||||
{
|
{
|
||||||
return rtrim(rtrim(number_format((float) $value, 4, '.', ''), '0'), '.');
|
return rtrim(rtrim(number_format((float) $value, 4, '.', ''), '0'), '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Relation
|
||||||
|
public function cutting(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Cutting::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rawMaterialPrice(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(RawMaterialPrice::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,17 +12,20 @@
|
|||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
class CuttingResult extends Model
|
class CuttingResult extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'cutting_result' => 'integer',
|
'cutting_result' => 'integer',
|
||||||
'sampel' => 'integer',
|
|
||||||
'hasil_cutting_diluar_sampel' => 'integer',
|
'hasil_cutting_diluar_sampel' => 'integer',
|
||||||
|
'sampel' => 'integer',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Relation
|
||||||
public function cutting(): BelongsTo
|
public function cutting(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Cutting::class);
|
return $this->belongsTo(Cutting::class);
|
||||||
|
|||||||
@ -12,28 +12,28 @@
|
|||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends(['price_formatted', 'cost_per_unit_formatted', 'type_label'])]
|
#[Appends(['cost_per_unit_formatted', 'price_formatted', 'type_label'])]
|
||||||
class CuttingResultPrice extends Model
|
class CuttingResultPrice extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, InteractsWithActivityLog;
|
use HasFactory, InteractsWithActivityLog;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'price_type' => PriceType::class,
|
|
||||||
'price' => 'integer',
|
|
||||||
'cost_per_unit' => 'integer',
|
'cost_per_unit' => 'integer',
|
||||||
|
'price' => 'integer',
|
||||||
|
'price_type' => PriceType::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cutting(): BelongsTo
|
// 3. Attribute
|
||||||
|
public function costPerUnitFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Cutting::class);
|
return Attribute::make(
|
||||||
}
|
get: fn () => 'Rp '.number_format($this->cost_per_unit, 0, ',', '.'),
|
||||||
|
);
|
||||||
public function productVariant(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(ProductVariant::class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function priceFormatted(): Attribute
|
public function priceFormatted(): Attribute
|
||||||
@ -43,17 +43,21 @@ public function priceFormatted(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function costPerUnitFormatted(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: fn () => 'Rp '.number_format($this->cost_per_unit, 0, ',', '.'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function typeLabel(): Attribute
|
public function typeLabel(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: fn () => $this->price_type->label(),
|
get: fn () => $this->price_type->label(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Relation
|
||||||
|
public function cutting(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Cutting::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function productVariant(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ProductVariant::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,46 +17,50 @@
|
|||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends(['base_salary_formatted', 'join_date_formatted', 'resign_date_formatted', 'join_date_input', 'employment_status_label'])]
|
#[Appends(['base_salary_formatted', 'employment_status_label', 'join_date_formatted', 'join_date_input', 'resign_date_formatted'])]
|
||||||
class Employee extends Model
|
class Employee extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
'base_salary' => 'integer',
|
||||||
|
'employment_status' => EmploymentStatus::class,
|
||||||
'join_date' => 'date',
|
'join_date' => 'date',
|
||||||
'resign_date' => 'date',
|
'resign_date' => 'date',
|
||||||
'employment_status' => EmploymentStatus::class,
|
|
||||||
'base_salary' => 'integer',
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function advances(): HasMany
|
// 3. Scope (grouped by column, then alphabetical)
|
||||||
|
// Column Group: employment_status
|
||||||
|
#[Scope]
|
||||||
|
protected function contract(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->hasMany(EmployeeAdvance::class);
|
$query->where('employment_status', EmploymentStatus::CONTRACT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function attendances(): HasMany
|
#[Scope]
|
||||||
|
protected function fullTime(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->hasMany(Attendance::class);
|
$query->where('employment_status', EmploymentStatus::FULL_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function leaveRequests(): HasMany
|
#[Scope]
|
||||||
|
protected function partTime(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->hasMany(LeaveRequest::class);
|
$query->where('employment_status', EmploymentStatus::PART_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function payrolls(): HasMany
|
#[Scope]
|
||||||
|
protected function temporary(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->hasMany(Payroll::class);
|
$query->where('employment_status', EmploymentStatus::TEMPORARY);
|
||||||
}
|
|
||||||
|
|
||||||
public function user(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(User::class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Attribute
|
||||||
public function baseSalaryFormatted(): Attribute
|
public function baseSalaryFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -92,27 +96,29 @@ public function resignDateFormatted(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
// 5. Relation
|
||||||
public function contract(Builder $query): void
|
public function advances(): HasMany
|
||||||
{
|
{
|
||||||
$query->where('employment_status', EmploymentStatus::CONTRACT->value);
|
return $this->hasMany(EmployeeAdvance::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
public function attendances(): HasMany
|
||||||
public function fullTime(Builder $query): void
|
|
||||||
{
|
{
|
||||||
$query->where('employment_status', EmploymentStatus::FULL_TIME->value);
|
return $this->hasMany(Attendance::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
public function leaveRequests(): HasMany
|
||||||
public function partTime(Builder $query): void
|
|
||||||
{
|
{
|
||||||
$query->where('employment_status', EmploymentStatus::PART_TIME->value);
|
return $this->hasMany(LeaveRequest::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
public function payrolls(): HasMany
|
||||||
public function temporary(Builder $query): void
|
|
||||||
{
|
{
|
||||||
$query->where('employment_status', EmploymentStatus::TEMPORARY->value);
|
return $this->hasMany(Payroll::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,65 +18,70 @@
|
|||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends([
|
#[Appends([
|
||||||
'amount_formatted',
|
'amount_formatted',
|
||||||
'paid_amount_formatted',
|
'can_pay',
|
||||||
'remaining_amount',
|
'can_verify',
|
||||||
'remaining_amount_formatted',
|
'created_at_formatted',
|
||||||
'due_date_formatted',
|
'due_date_formatted',
|
||||||
'due_date_input',
|
'due_date_input',
|
||||||
'status_label',
|
|
||||||
'employee_name',
|
'employee_name',
|
||||||
'rejection_reason',
|
|
||||||
'created_at_formatted',
|
|
||||||
'is_editable',
|
'is_editable',
|
||||||
'can_verify',
|
'paid_amount_formatted',
|
||||||
'can_pay',
|
'rejection_reason',
|
||||||
|
'remaining_amount',
|
||||||
|
'remaining_amount_formatted',
|
||||||
|
'status_label',
|
||||||
])]
|
])]
|
||||||
class EmployeeAdvance extends Model
|
class EmployeeAdvance extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, HasRejection, InteractsWithActivityLog;
|
use HasFactory, HasRejection, InteractsWithActivityLog;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'amount' => 'integer',
|
'amount' => 'integer',
|
||||||
'paid_amount' => 'integer',
|
|
||||||
'due_date' => 'date',
|
'due_date' => 'date',
|
||||||
|
'paid_amount' => 'integer',
|
||||||
|
'paid_at' => 'datetime',
|
||||||
'status' => EmployeeAdvanceStatus::class,
|
'status' => EmployeeAdvanceStatus::class,
|
||||||
'verified_at' => 'datetime',
|
'verified_at' => 'datetime',
|
||||||
'paid_at' => 'datetime',
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cashTransaction(): BelongsTo
|
// 3. Scope (grouped by column, then alphabetical)
|
||||||
|
// Column Group: status
|
||||||
|
#[Scope]
|
||||||
|
protected function approved(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(CashTransaction::class);
|
$query->where('status', EmployeeAdvanceStatus::APPROVED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function employee(): BelongsTo
|
#[Scope]
|
||||||
|
protected function paid(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Employee::class);
|
$query->where('status', EmployeeAdvanceStatus::PAID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function paidBy(): BelongsTo
|
#[Scope]
|
||||||
|
protected function partiallyPaid(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'paid_by_id');
|
$query->where('status', EmployeeAdvanceStatus::PARTIALLY_PAID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function repaymentCashTransaction(): BelongsTo
|
#[Scope]
|
||||||
|
protected function pending(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(CashTransaction::class, 'repayment_cash_transaction_id');
|
$query->where('status', EmployeeAdvanceStatus::PENDING);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function verifiedBy(): BelongsTo
|
#[Scope]
|
||||||
|
protected function rejected(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'verified_by_id');
|
$query->where('status', EmployeeAdvanceStatus::REJECTED);
|
||||||
}
|
|
||||||
|
|
||||||
public function payments(): HasMany
|
|
||||||
{
|
|
||||||
return $this->hasMany(EmployeeAdvancePayment::class)->latest('paid_at');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Attribute
|
||||||
public function amountFormatted(): Attribute
|
public function amountFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -84,27 +89,6 @@ public function amountFormatted(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function paidAmountFormatted(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: fn () => 'Rp '.number_format($this->paid_amount, 0, ',', '.'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function remainingAmount(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: fn () => $this->amount - $this->paid_amount,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function remainingAmountFormatted(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: fn () => 'Rp '.number_format($this->remaining_amount, 0, ',', '.'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function canPay(): Attribute
|
public function canPay(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -155,6 +139,13 @@ public function isEditable(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function paidAmountFormatted(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => 'Rp '.number_format($this->paid_amount, 0, ',', '.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function rejectionReason(): Attribute
|
public function rejectionReason(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -162,6 +153,20 @@ public function rejectionReason(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function remainingAmount(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => $this->amount - $this->paid_amount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remainingAmountFormatted(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => 'Rp '.number_format($this->remaining_amount, 0, ',', '.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function statusLabel(): Attribute
|
public function statusLabel(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -169,21 +174,34 @@ public function statusLabel(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
// 5. Relation
|
||||||
public function pending(Builder $query): void
|
public function cashTransaction(): BelongsTo
|
||||||
{
|
{
|
||||||
$query->where('status', EmployeeAdvanceStatus::PENDING);
|
return $this->belongsTo(CashTransaction::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
public function employee(): BelongsTo
|
||||||
public function approved(Builder $query): void
|
|
||||||
{
|
{
|
||||||
$query->where('status', EmployeeAdvanceStatus::APPROVED);
|
return $this->belongsTo(Employee::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
public function paidBy(): BelongsTo
|
||||||
public function paid(Builder $query): void
|
|
||||||
{
|
{
|
||||||
$query->where('status', EmployeeAdvanceStatus::PAID);
|
return $this->belongsTo(User::class, 'paid_by_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function payments(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EmployeeAdvancePayment::class)->latest('paid_at');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function repaymentCashTransaction(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(CashTransaction::class, 'repayment_cash_transaction_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verifiedBy(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'verified_by_id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Attributes\Appends;
|
use Illuminate\Database\Eloquent\Attributes\Appends;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
@ -15,8 +16,10 @@
|
|||||||
])]
|
])]
|
||||||
class EmployeeAdvancePayment extends Model
|
class EmployeeAdvancePayment extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -25,6 +28,27 @@ protected function casts(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Attribute
|
||||||
|
public function amountFormatted(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => 'Rp '.number_format($this->amount, 0, ',', '.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paidAtFormatted(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => $this->paid_at?->translatedFormat('l, d F Y H:i'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Relation
|
||||||
|
public function cashTransaction(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(CashTransaction::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function employeeAdvance(): BelongsTo
|
public function employeeAdvance(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(EmployeeAdvance::class);
|
return $this->belongsTo(EmployeeAdvance::class);
|
||||||
@ -34,23 +58,4 @@ public function paidBy(): BelongsTo
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'paid_by_id');
|
return $this->belongsTo(User::class, 'paid_by_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cashTransaction(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(CashTransaction::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function amountFormatted(): \Illuminate\Database\Eloquent\Casts\Attribute
|
|
||||||
{
|
|
||||||
return \Illuminate\Database\Eloquent\Casts\Attribute::make(
|
|
||||||
get: fn () => 'Rp '.number_format($this->amount, 0, ',', '.'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function paidAtFormatted(): \Illuminate\Database\Eloquent\Casts\Attribute
|
|
||||||
{
|
|
||||||
return \Illuminate\Database\Eloquent\Casts\Attribute::make(
|
|
||||||
get: fn () => $this->paid_at?->translatedFormat('l, d F Y H:i'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,8 +17,10 @@
|
|||||||
#[Appends(['amount_formatted', 'created_at_formatted', 'created_by_name'])]
|
#[Appends(['amount_formatted', 'created_at_formatted', 'created_by_name'])]
|
||||||
class Expense extends Model implements HasMedia
|
class Expense extends Model implements HasMedia
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, HasModuleMedia, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, HasModuleMedia, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -26,16 +28,7 @@ protected function casts(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cashTransaction(): BelongsTo
|
// 3. Attribute
|
||||||
{
|
|
||||||
return $this->belongsTo(CashTransaction::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createdBy(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(User::class, 'created_by_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function amountFormatted(): Attribute
|
public function amountFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -57,6 +50,7 @@ public function createdByName(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Other Methods
|
||||||
public static function mediaModuleName(): string
|
public static function mediaModuleName(): string
|
||||||
{
|
{
|
||||||
return 'expense';
|
return 'expense';
|
||||||
@ -66,4 +60,15 @@ public function registerMediaCollections(): void
|
|||||||
{
|
{
|
||||||
$this->addMediaCollection('photos');
|
$this->addMediaCollection('photos');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Relation
|
||||||
|
public function cashTransaction(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(CashTransaction::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createdBy(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'created_by_id');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,22 +12,24 @@
|
|||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
class HomepageConfiguration extends Model implements HasMedia
|
class HomepageConfiguration extends Model implements HasMedia
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, HasModuleMedia, InteractsWithActivityLog;
|
use HasFactory, HasModuleMedia, InteractsWithActivityLog;
|
||||||
|
|
||||||
|
// 2. Other Methods
|
||||||
|
public static function instance(): self
|
||||||
|
{
|
||||||
|
return static::query()->firstOrCreate(['id' => 1]);
|
||||||
|
}
|
||||||
|
|
||||||
public static function mediaModuleName(): string
|
public static function mediaModuleName(): string
|
||||||
{
|
{
|
||||||
return 'homepage';
|
return 'homepage';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function instance(): self
|
|
||||||
{
|
|
||||||
return static::query()->firstOrCreate(['id' => 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function registerMediaCollections(): void
|
public function registerMediaCollections(): void
|
||||||
{
|
{
|
||||||
$this->addMediaCollection('hero_image')->singleFile();
|
|
||||||
$this->addMediaCollection('about_image')->singleFile();
|
$this->addMediaCollection('about_image')->singleFile();
|
||||||
$this->addMediaCollection('gallery');
|
$this->addMediaCollection('gallery');
|
||||||
|
$this->addMediaCollection('hero_image')->singleFile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,42 +16,55 @@
|
|||||||
|
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends([
|
#[Appends([
|
||||||
'start_date_formatted',
|
|
||||||
'end_date_formatted',
|
|
||||||
'start_date_input',
|
|
||||||
'end_date_input',
|
|
||||||
'status_label',
|
|
||||||
'employee_name',
|
|
||||||
'rejection_reason',
|
|
||||||
'created_at_formatted',
|
|
||||||
'is_editable',
|
|
||||||
'can_verify',
|
'can_verify',
|
||||||
|
'created_at_formatted',
|
||||||
|
'employee_name',
|
||||||
|
'end_date_formatted',
|
||||||
|
'end_date_input',
|
||||||
|
'is_editable',
|
||||||
|
'rejection_reason',
|
||||||
|
'start_date_formatted',
|
||||||
|
'start_date_input',
|
||||||
|
'status_label',
|
||||||
])]
|
])]
|
||||||
class LeaveRequest extends Model
|
class LeaveRequest extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, HasRejection, InteractsWithActivityLog;
|
use HasFactory, HasRejection, InteractsWithActivityLog;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'start_date' => 'date',
|
|
||||||
'end_date' => 'date',
|
'end_date' => 'date',
|
||||||
'total_days' => 'integer',
|
'start_date' => 'date',
|
||||||
'status' => LeaveRequestStatus::class,
|
'status' => LeaveRequestStatus::class,
|
||||||
|
'total_days' => 'integer',
|
||||||
'verified_at' => 'datetime',
|
'verified_at' => 'datetime',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function employee(): BelongsTo
|
// 3. Scope (grouped by column, then alphabetical)
|
||||||
|
// Column Group: status
|
||||||
|
#[Scope]
|
||||||
|
protected function approved(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Employee::class);
|
$query->where('status', LeaveRequestStatus::APPROVED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function verifiedBy(): BelongsTo
|
#[Scope]
|
||||||
|
protected function pending(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'verified_by_id');
|
$query->where('status', LeaveRequestStatus::PENDING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function rejected(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('status', LeaveRequestStatus::REJECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Attribute
|
||||||
public function canVerify(): Attribute
|
public function canVerify(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -123,15 +136,14 @@ public function statusLabel(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
// 5. Relation
|
||||||
public function pending(Builder $query): void
|
public function employee(): BelongsTo
|
||||||
{
|
{
|
||||||
$query->where('status', LeaveRequestStatus::PENDING);
|
return $this->belongsTo(Employee::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
public function verifiedBy(): BelongsTo
|
||||||
public function approved(Builder $query): void
|
|
||||||
{
|
{
|
||||||
$query->where('status', LeaveRequestStatus::APPROVED);
|
return $this->belongsTo(User::class, 'verified_by_id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
class Notification extends Model
|
class Notification extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -19,11 +20,21 @@ protected function casts(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function user(): BelongsTo
|
// 2. Scope (grouped by column, then alphabetical)
|
||||||
|
// Column Group: is_read
|
||||||
|
#[Scope]
|
||||||
|
protected function read(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
$query->where('is_read', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function unread(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('is_read', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Other Methods
|
||||||
public function markAsRead(): void
|
public function markAsRead(): void
|
||||||
{
|
{
|
||||||
if (! $this->is_read) {
|
if (! $this->is_read) {
|
||||||
@ -31,9 +42,9 @@ public function markAsRead(): void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
// 4. Relation
|
||||||
public function unread(Builder $query): void
|
public function user(): BelongsTo
|
||||||
{
|
{
|
||||||
$query->where('is_read', false);
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,63 +22,172 @@
|
|||||||
|
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends([
|
#[Appends([
|
||||||
'subtotal_formatted',
|
|
||||||
'discount_formatted',
|
|
||||||
'nego_price_formatted',
|
|
||||||
'total_amount_formatted',
|
|
||||||
'created_at_formatted',
|
|
||||||
'channel_label',
|
'channel_label',
|
||||||
|
'creator_name',
|
||||||
|
'discount_formatted',
|
||||||
|
'marketing_name',
|
||||||
|
'nego_price_formatted',
|
||||||
'payment_type_label',
|
'payment_type_label',
|
||||||
'price_type_label',
|
'price_type_label',
|
||||||
'status_label',
|
'status_label',
|
||||||
'creator_name',
|
'subtotal_formatted',
|
||||||
'marketing_name',
|
'total_amount_formatted',
|
||||||
|
'created_at_formatted',
|
||||||
])]
|
])]
|
||||||
class Order extends Model implements HasMedia
|
class Order extends Model implements HasMedia
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, HasModuleMedia, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, HasModuleMedia, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'channel' => OrderChannel::class,
|
'channel' => OrderChannel::class,
|
||||||
'price_type' => PriceType::class,
|
|
||||||
'payment_type' => PaymentType::class,
|
|
||||||
'is_affiliate' => 'boolean',
|
'is_affiliate' => 'boolean',
|
||||||
|
'marketplace_settings_snapshot' => 'array',
|
||||||
|
'nego_price' => 'integer',
|
||||||
|
'payment_type' => PaymentType::class,
|
||||||
|
'price_type' => PriceType::class,
|
||||||
'status' => OrderStatus::class,
|
'status' => OrderStatus::class,
|
||||||
'subtotal' => 'integer',
|
'subtotal' => 'integer',
|
||||||
'discount' => 'integer',
|
|
||||||
'nego_price' => 'integer',
|
|
||||||
'marketplace_settings_snapshot' => 'array',
|
|
||||||
'total_amount' => 'integer',
|
'total_amount' => 'integer',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cashTransaction(): BelongsTo
|
// 3. Scope (grouped by column, then alphabetical)
|
||||||
|
// Column Group: channel
|
||||||
|
#[Scope]
|
||||||
|
protected function shopee(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(CashTransaction::class);
|
$query->where('channel', OrderChannel::SHOPEE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createdBy(): BelongsTo
|
#[Scope]
|
||||||
|
protected function store(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'created_by_id');
|
$query->where('channel', OrderChannel::STORE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function customer(): BelongsTo
|
#[Scope]
|
||||||
|
protected function tiktok(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Customer::class);
|
$query->where('channel', OrderChannel::TIKTOK);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function items(): HasMany
|
// Column Group: is_affiliate
|
||||||
|
#[Scope]
|
||||||
|
protected function affiliate(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->hasMany(OrderItem::class);
|
$query->where('is_affiliate', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function marketing(): BelongsTo
|
#[Scope]
|
||||||
|
protected function nonAffiliate(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'marketing_id');
|
$query->where('is_affiliate', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Column Group: payment_type
|
||||||
|
#[Scope]
|
||||||
|
protected function cash(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('payment_type', PaymentType::CASH);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function marketplace(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('payment_type', PaymentType::MARKETPLACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function qris(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('payment_type', PaymentType::QRIS);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function transfer(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('payment_type', PaymentType::TRANSFER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column Group: price_type
|
||||||
|
#[Scope]
|
||||||
|
protected function agent(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('price_type', PriceType::AGENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function distributor(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('price_type', PriceType::DISTRIBUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function ecer(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('price_type', PriceType::ECER);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function grosir(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('price_type', PriceType::GROSIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function hargaModal(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('price_type', PriceType::HARGA_MODAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function shopeePrice(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('price_type', PriceType::SHOPEE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function subAgent(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('price_type', PriceType::SUB_AGENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function tiktokPrice(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('price_type', PriceType::TIKTOK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column Group: status
|
||||||
|
#[Scope]
|
||||||
|
protected function cancelled(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('status', OrderStatus::CANCELLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function completed(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('status', OrderStatus::COMPLETED);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function pending(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('status', OrderStatus::PENDING);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function processing(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('status', OrderStatus::PROCESSING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Attribute
|
||||||
public function channelLabel(): Attribute
|
public function channelLabel(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -93,6 +202,13 @@ public function createdAtFormatted(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function creatorName(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => $this->createdBy?->profile?->full_name ?? $this->createdBy?->username ?? '-',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function discountFormatted(): Attribute
|
public function discountFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -100,6 +216,13 @@ public function discountFormatted(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function marketingName(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => $this->marketing?->profile?->full_name ?? $this->marketing?->username ?? '-',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function negoPriceFormatted(): Attribute
|
public function negoPriceFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -107,13 +230,6 @@ public function negoPriceFormatted(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function totalAmountFormatted(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: fn () => 'Rp '.number_format($this->total_amount, 0, ',', '.'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function paymentTypeLabel(): Attribute
|
public function paymentTypeLabel(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -135,20 +251,6 @@ public function statusLabel(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function creatorName(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: fn () => $this->createdBy?->profile?->full_name ?? $this->createdBy?->username ?? '-',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function marketingName(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: fn () => $this->marketing?->profile?->full_name ?? $this->marketing?->username ?? '-',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function subtotalFormatted(): Attribute
|
public function subtotalFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -156,6 +258,14 @@ public function subtotalFormatted(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function totalAmountFormatted(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => 'Rp '.number_format($this->total_amount, 0, ',', '.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Other Methods
|
||||||
public static function mediaModuleName(): string
|
public static function mediaModuleName(): string
|
||||||
{
|
{
|
||||||
return 'order';
|
return 'order';
|
||||||
@ -166,9 +276,29 @@ public function registerMediaCollections(): void
|
|||||||
$this->addMediaCollection('photos');
|
$this->addMediaCollection('photos');
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
// 6. Relation
|
||||||
public function completed(Builder $query): void
|
public function cashTransaction(): BelongsTo
|
||||||
{
|
{
|
||||||
$query->where('status', OrderStatus::COMPLETED);
|
return $this->belongsTo(CashTransaction::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createdBy(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'created_by_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function customer(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Customer::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function items(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(OrderItem::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function marketing(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'marketing_id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,38 +16,26 @@
|
|||||||
#[Appends([
|
#[Appends([
|
||||||
'quantity_formatted',
|
'quantity_formatted',
|
||||||
'quantity_input',
|
'quantity_input',
|
||||||
'unit_price_formatted',
|
|
||||||
'subtotal_formatted',
|
'subtotal_formatted',
|
||||||
|
'unit_price_formatted',
|
||||||
])]
|
])]
|
||||||
class OrderItem extends Model
|
class OrderItem extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'stock_quality' => ProductStockQuality::class,
|
|
||||||
'quantity' => 'integer',
|
'quantity' => 'integer',
|
||||||
'unit_price' => 'integer',
|
'stock_quality' => ProductStockQuality::class,
|
||||||
'subtotal' => 'integer',
|
'subtotal' => 'integer',
|
||||||
|
'unit_price' => 'integer',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function order(): BelongsTo
|
// 3. Attribute
|
||||||
{
|
|
||||||
return $this->belongsTo(Order::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function productVariant(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(ProductVariant::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function user(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(User::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function quantityFormatted(): Attribute
|
public function quantityFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -75,4 +63,20 @@ public function unitPriceFormatted(): Attribute
|
|||||||
get: fn () => 'Rp '.number_format($this->unit_price, 0, ',', '.'),
|
get: fn () => 'Rp '.number_format($this->unit_price, 0, ',', '.'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Relation
|
||||||
|
public function order(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Order::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function productVariant(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ProductVariant::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,43 +23,68 @@
|
|||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends([
|
#[Appends([
|
||||||
'action_label',
|
'action_label',
|
||||||
|
'created_at_formatted',
|
||||||
|
'is_pending',
|
||||||
'status_label',
|
'status_label',
|
||||||
'subject_label',
|
'subject_label',
|
||||||
'submitted_by_name',
|
'submitted_by_name',
|
||||||
'verified_by_name',
|
|
||||||
'created_at_formatted',
|
|
||||||
'verified_at_formatted',
|
'verified_at_formatted',
|
||||||
'is_pending',
|
'verified_by_name',
|
||||||
])]
|
])]
|
||||||
class OwnerVerificationRequest extends Model implements HasMedia
|
class OwnerVerificationRequest extends Model implements HasMedia
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, HasModuleMedia, HasRejection, InteractsWithActivityLog;
|
use HasFactory, HasModuleMedia, HasRejection, InteractsWithActivityLog;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'action' => OwnerVerificationAction::class,
|
'action' => OwnerVerificationAction::class,
|
||||||
'status' => OwnerVerificationStatus::class,
|
|
||||||
'payload' => 'array',
|
'payload' => 'array',
|
||||||
|
'status' => OwnerVerificationStatus::class,
|
||||||
'verified_at' => 'datetime',
|
'verified_at' => 'datetime',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function subject(): MorphTo
|
// 3. Scope (grouped by column, then alphabetical)
|
||||||
|
// Column Group: status
|
||||||
|
#[Scope]
|
||||||
|
protected function approved(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->morphTo();
|
$query->where('status', OwnerVerificationStatus::APPROVED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submittedBy(): BelongsTo
|
#[Scope]
|
||||||
|
protected function pending(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'submitted_by_id');
|
$query->where('status', OwnerVerificationStatus::PENDING);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function verifiedBy(): BelongsTo
|
#[Scope]
|
||||||
|
protected function rejected(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'verified_by_id');
|
$query->where('status', OwnerVerificationStatus::REJECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scope lainnya
|
||||||
|
#[Scope]
|
||||||
|
protected function forSubmitter(Builder $query, User $user): void
|
||||||
|
{
|
||||||
|
$query->where('submitted_by_id', $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function visibleTo(Builder $query, User $user): void
|
||||||
|
{
|
||||||
|
if ($user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->forSubmitter($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Attribute
|
||||||
public function actionLabel(): Attribute
|
public function actionLabel(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -119,38 +144,10 @@ public function verifiedByName(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
// 5. Other Methods
|
||||||
public function approved(Builder $query): void
|
public static function mediaModuleName(): string
|
||||||
{
|
{
|
||||||
$query->where('status', OwnerVerificationStatus::APPROVED);
|
return 'owner_verification_request';
|
||||||
}
|
|
||||||
|
|
||||||
#[Scope]
|
|
||||||
public function pending(Builder $query): void
|
|
||||||
{
|
|
||||||
$query->where('status', OwnerVerificationStatus::PENDING);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Scope]
|
|
||||||
public function rejected(Builder $query): void
|
|
||||||
{
|
|
||||||
$query->where('status', OwnerVerificationStatus::REJECTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Scope]
|
|
||||||
public function forSubmitter(Builder $query, User $user): void
|
|
||||||
{
|
|
||||||
$query->where('submitted_by_id', $user->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Scope]
|
|
||||||
public function visibleTo(Builder $query, User $user): void
|
|
||||||
{
|
|
||||||
if ($user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$query->forSubmitter($user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function pendingToggleIsActive(): ?bool
|
public function pendingToggleIsActive(): ?bool
|
||||||
@ -169,9 +166,9 @@ public function pendingToggleIsActive(): ?bool
|
|||||||
return (bool) $new['is_active'];
|
return (bool) $new['is_active'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function mediaModuleName(): string
|
public function priceImageCollection(int $index): string
|
||||||
{
|
{
|
||||||
return 'owner_verification_request';
|
return "price_images_{$index}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function variantImageCollection(int $index): string
|
public function variantImageCollection(int $index): string
|
||||||
@ -179,8 +176,19 @@ public function variantImageCollection(int $index): string
|
|||||||
return "variant_images_{$index}";
|
return "variant_images_{$index}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function priceImageCollection(int $index): string
|
// 6. Relation
|
||||||
|
public function subject(): MorphTo
|
||||||
{
|
{
|
||||||
return "price_images_{$index}";
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submittedBy(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'submitted_by_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verifiedBy(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'verified_by_id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Enums\EmployeeAdvanceStatus;
|
|
||||||
use App\Enums\PayrollAdjustmentType;
|
use App\Enums\PayrollAdjustmentType;
|
||||||
use App\Enums\PayrollStatus;
|
use App\Enums\PayrollStatus;
|
||||||
use App\Models\Concerns\InteractsWithActivityLog;
|
use App\Models\Concerns\InteractsWithActivityLog;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Appends;
|
use Illuminate\Database\Eloquent\Attributes\Appends;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
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\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
@ -18,54 +19,46 @@
|
|||||||
#[Appends([
|
#[Appends([
|
||||||
'base_salary_formatted',
|
'base_salary_formatted',
|
||||||
'bonus_amount_formatted',
|
'bonus_amount_formatted',
|
||||||
|
'can_adjust',
|
||||||
'deduction_amount_formatted',
|
'deduction_amount_formatted',
|
||||||
'total_amount_formatted',
|
|
||||||
'status_label',
|
|
||||||
'employee_name',
|
'employee_name',
|
||||||
'paid_at_formatted',
|
'paid_at_formatted',
|
||||||
'can_adjust',
|
'status_label',
|
||||||
|
'total_amount_formatted',
|
||||||
])]
|
])]
|
||||||
class Payroll extends Model
|
class Payroll extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, InteractsWithActivityLog;
|
use HasFactory, InteractsWithActivityLog;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'base_salary' => 'integer',
|
'base_salary' => 'integer',
|
||||||
'bonus_amount' => 'integer',
|
'bonus_amount' => 'integer',
|
||||||
'deduction_amount' => 'integer',
|
'deduction_amount' => 'integer',
|
||||||
'total_amount' => 'integer',
|
|
||||||
'status' => PayrollStatus::class,
|
|
||||||
'paid_at' => 'datetime',
|
'paid_at' => 'datetime',
|
||||||
|
'status' => PayrollStatus::class,
|
||||||
|
'total_amount' => 'integer',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function adjustments(): HasMany
|
// 3. Scope (grouped by column, then alphabetical)
|
||||||
|
// Column Group: status
|
||||||
|
#[Scope]
|
||||||
|
protected function paid(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->hasMany(PayrollAdjustment::class);
|
$query->where('status', PayrollStatus::PAID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cashTransaction(): BelongsTo
|
#[Scope]
|
||||||
|
protected function unpaid(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(CashTransaction::class);
|
$query->where('status', PayrollStatus::UNPAID);
|
||||||
}
|
|
||||||
|
|
||||||
public function employee(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Employee::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function paidBy(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(User::class, 'paid_by_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function payrollPeriod(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(PayrollPeriod::class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Attribute
|
||||||
public function baseSalaryFormatted(): Attribute
|
public function baseSalaryFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -104,13 +97,6 @@ public function employeeName(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function totalAmountFormatted(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: fn () => 'Rp '.number_format($this->total_amount, 0, ',', '.'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function paidAtFormatted(): Attribute
|
public function paidAtFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -125,7 +111,15 @@ public function statusLabel(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function calculateKasbonDeduction(): int
|
public function totalAmountFormatted(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => 'Rp '.number_format($this->total_amount, 0, ',', '.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Other Methods
|
||||||
|
public function calculateAdvanceDeduction(): int
|
||||||
{
|
{
|
||||||
$outstanding = (int) EmployeeAdvance::query()
|
$outstanding = (int) EmployeeAdvance::query()
|
||||||
->where('employee_id', $this->employee_id)
|
->where('employee_id', $this->employee_id)
|
||||||
@ -148,10 +142,36 @@ public function recalculateAmounts(): void
|
|||||||
->where('type', PayrollAdjustmentType::DEDUCTION)
|
->where('type', PayrollAdjustmentType::DEDUCTION)
|
||||||
->sum('amount');
|
->sum('amount');
|
||||||
|
|
||||||
$kasbonDeduction = $this->calculateKasbonDeduction();
|
$advanceDeduction = $this->calculateAdvanceDeduction();
|
||||||
|
|
||||||
$this->bonus_amount = $bonusAmount;
|
$this->bonus_amount = $bonusAmount;
|
||||||
$this->deduction_amount = $kasbonDeduction + $manualDeduction;
|
$this->deduction_amount = $advanceDeduction + $manualDeduction;
|
||||||
$this->total_amount = max(0, (int) $this->base_salary + $bonusAmount - $this->deduction_amount);
|
$this->total_amount = max(0, (int) $this->base_salary + $bonusAmount - $this->deduction_amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 6. Relation
|
||||||
|
public function adjustments(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(PayrollAdjustment::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cashTransaction(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(CashTransaction::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function employee(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Employee::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paidBy(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'paid_by_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function payrollPeriod(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(PayrollPeriod::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,37 +14,39 @@
|
|||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends(['amount_formatted', 'type_label', 'created_at_formatted', 'created_by_name'])]
|
#[Appends(['amount_formatted', 'created_at_formatted', 'created_by_name', 'type_label'])]
|
||||||
class PayrollAdjustment extends Model
|
class PayrollAdjustment extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, InteractsWithActivityLog;
|
use HasFactory, InteractsWithActivityLog;
|
||||||
|
|
||||||
public $timestamps = false;
|
public $timestamps = false;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'type' => PayrollAdjustmentType::class,
|
|
||||||
'amount' => 'integer',
|
'amount' => 'integer',
|
||||||
'created_at' => 'datetime',
|
'created_at' => 'datetime',
|
||||||
|
'type' => PayrollAdjustmentType::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function attendance(): BelongsTo
|
// 3. Scope (grouped by column, then alphabetical)
|
||||||
|
// Column Group: type
|
||||||
|
#[Scope]
|
||||||
|
protected function bonus(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Attendance::class);
|
$query->where('type', PayrollAdjustmentType::BONUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createdBy(): BelongsTo
|
#[Scope]
|
||||||
|
protected function deduction(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'created_by_id');
|
$query->where('type', PayrollAdjustmentType::DEDUCTION);
|
||||||
}
|
|
||||||
|
|
||||||
public function payroll(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Payroll::class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Attribute
|
||||||
public function amountFormatted(): Attribute
|
public function amountFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -73,15 +75,19 @@ public function typeLabel(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
// 5. Relation
|
||||||
public function deduction(Builder $query): void
|
public function attendance(): BelongsTo
|
||||||
{
|
{
|
||||||
$query->where('type', PayrollAdjustmentType::DEDUCTION);
|
return $this->belongsTo(Attendance::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
public function createdBy(): BelongsTo
|
||||||
public function bonus(Builder $query): void
|
|
||||||
{
|
{
|
||||||
$query->where('type', PayrollAdjustmentType::BONUS);
|
return $this->belongsTo(User::class, 'created_by_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function payroll(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Payroll::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,38 +8,45 @@
|
|||||||
use Illuminate\Database\Eloquent\Attributes\Appends;
|
use Illuminate\Database\Eloquent\Attributes\Appends;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Scope;
|
use Illuminate\Database\Eloquent\Attributes\Scope;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Database\Query\Builder;
|
|
||||||
|
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends(['period_label', 'status_label', 'closed_at_formatted'])]
|
#[Appends(['closed_at_formatted', 'period_label', 'status_label'])]
|
||||||
class PayrollPeriod extends Model
|
class PayrollPeriod extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'status' => PayrollPeriodStatus::class,
|
|
||||||
'closed_at' => 'datetime',
|
'closed_at' => 'datetime',
|
||||||
|
'status' => PayrollPeriodStatus::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function closedBy(): BelongsTo
|
// 3. Scope (grouped by column, then alphabetical)
|
||||||
|
// Column Group: status
|
||||||
|
#[Scope]
|
||||||
|
protected function closed(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'closed_by_id');
|
$query->where('status', PayrollPeriodStatus::CLOSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function payrolls(): HasMany
|
#[Scope]
|
||||||
|
protected function open(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->hasMany(Payroll::class);
|
$query->where('status', PayrollPeriodStatus::OPEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Attribute
|
||||||
public function closedAtFormatted(): Attribute
|
public function closedAtFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -61,14 +68,20 @@ public function statusLabel(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Other Methods
|
||||||
public function isOpen(): bool
|
public function isOpen(): bool
|
||||||
{
|
{
|
||||||
return $this->status === PayrollPeriodStatus::OPEN;
|
return $this->status === PayrollPeriodStatus::OPEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
// 6. Relation
|
||||||
public function open(Builder $query): void
|
public function closedBy(): BelongsTo
|
||||||
{
|
{
|
||||||
$query->where('status', PayrollPeriodStatus::OPEN);
|
return $this->belongsTo(User::class, 'closed_by_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function payrolls(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Payroll::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,8 +26,10 @@
|
|||||||
#[Sluggable(from: 'name', to: 'slug')]
|
#[Sluggable(from: 'name', to: 'slug')]
|
||||||
class Product extends Model
|
class Product extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, HasPendingOwnerVerification, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, HasPendingOwnerVerification, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -35,16 +37,40 @@ protected function casts(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Scope (grouped by column, then alphabetical)
|
||||||
|
// Column Group: is_active
|
||||||
|
#[Scope]
|
||||||
|
protected function active(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('is_active', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function inactive(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('is_active', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Other Methods
|
||||||
|
public static function getActiveWithVariantsAndCategories(): Collection
|
||||||
|
{
|
||||||
|
return self::query()
|
||||||
|
->active()
|
||||||
|
->with([
|
||||||
|
'categories',
|
||||||
|
'variants' => fn ($query) => $query
|
||||||
|
->with('media')
|
||||||
|
->orderBy('created_at'),
|
||||||
|
])
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Relation
|
||||||
public function categories(): BelongsToMany
|
public function categories(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(Category::class, 'product_categories');
|
return $this->belongsToMany(Category::class, 'product_categories');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function variants(): HasMany
|
|
||||||
{
|
|
||||||
return $this->hasMany(ProductVariant::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function ownerVerificationRequests(): MorphMany
|
public function ownerVerificationRequests(): MorphMany
|
||||||
{
|
{
|
||||||
return $this->morphMany(OwnerVerificationRequest::class, 'subject');
|
return $this->morphMany(OwnerVerificationRequest::class, 'subject');
|
||||||
@ -57,28 +83,8 @@ public function pendingOwnerVerificationRequest(): MorphOne
|
|||||||
->latestOfMany();
|
->latestOfMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
public function variants(): HasMany
|
||||||
public function active(Builder $query): void
|
|
||||||
{
|
{
|
||||||
$query->where('is_active', true);
|
return $this->hasMany(ProductVariant::class);
|
||||||
}
|
|
||||||
|
|
||||||
#[Scope]
|
|
||||||
public function inactive(Builder $query): void
|
|
||||||
{
|
|
||||||
$query->where('is_active', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getActiveWithVariantsAndCategories(): Collection
|
|
||||||
{
|
|
||||||
return self::query()
|
|
||||||
->active()
|
|
||||||
->with([
|
|
||||||
'categories',
|
|
||||||
'variants' => fn ($query) => $query
|
|
||||||
->with('media')
|
|
||||||
->orderBy('created_at'),
|
|
||||||
])
|
|
||||||
->get();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,21 +14,19 @@
|
|||||||
#[Appends(['price_formatted', 'price_input'])]
|
#[Appends(['price_formatted', 'price_input'])]
|
||||||
class ProductPrice extends Model
|
class ProductPrice extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'type' => PriceType::class,
|
|
||||||
'price' => 'integer',
|
'price' => 'integer',
|
||||||
|
'type' => PriceType::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function variant(): BelongsTo
|
// 3. Attribute
|
||||||
{
|
|
||||||
return $this->belongsTo(ProductVariant::class, 'variant_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function priceFormatted(): Attribute
|
public function priceFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -42,4 +40,10 @@ public function priceInput(): Attribute
|
|||||||
get: fn () => (string) $this->price,
|
get: fn () => (string) $this->price,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Relation
|
||||||
|
public function variant(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ProductVariant::class, 'variant_id');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,19 +15,38 @@
|
|||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
class ProductVariant extends Model implements HasMedia
|
class ProductVariant extends Model implements HasMedia
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, HasModuleMedia, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, HasModuleMedia, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
private const MIN_STOCK = 5;
|
private const MIN_STOCK = 5;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'stock' => 'integer',
|
|
||||||
'reject_stock' => 'integer',
|
'reject_stock' => 'integer',
|
||||||
|
'stock' => 'integer',
|
||||||
'stock_ecer' => 'integer',
|
'stock_ecer' => 'integer',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Other Methods
|
||||||
|
public static function mediaModuleName(): string
|
||||||
|
{
|
||||||
|
return 'product';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function minStock(): int
|
||||||
|
{
|
||||||
|
return self::MIN_STOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerMediaCollections(): void
|
||||||
|
{
|
||||||
|
$this->addMediaCollection('images');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Relation
|
||||||
public function cuttingResultPrices(): HasMany
|
public function cuttingResultPrices(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(CuttingResultPrice::class);
|
return $this->hasMany(CuttingResultPrice::class);
|
||||||
@ -52,19 +71,4 @@ public function product(): BelongsTo
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(Product::class);
|
return $this->belongsTo(Product::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function minStock(): int
|
|
||||||
{
|
|
||||||
return self::MIN_STOCK;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function mediaModuleName(): string
|
|
||||||
{
|
|
||||||
return 'product';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function registerMediaCollections(): void
|
|
||||||
{
|
|
||||||
$this->addMediaCollection('images');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,53 +20,29 @@
|
|||||||
|
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends([
|
#[Appends([
|
||||||
'subtotal_formatted',
|
'created_at_formatted',
|
||||||
'discount_formatted',
|
'discount_formatted',
|
||||||
'shipping_cost_formatted',
|
'shipping_cost_formatted',
|
||||||
|
'subtotal_formatted',
|
||||||
'total_formatted',
|
'total_formatted',
|
||||||
'created_at_formatted',
|
|
||||||
])]
|
])]
|
||||||
class Purchase extends Model implements HasMedia
|
class Purchase extends Model implements HasMedia
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, HasModuleMedia, HasPendingOwnerVerification, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, HasModuleMedia, HasPendingOwnerVerification, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'subtotal' => 'integer',
|
|
||||||
'discount' => 'integer',
|
'discount' => 'integer',
|
||||||
'shipping_cost' => 'integer',
|
'shipping_cost' => 'integer',
|
||||||
|
'subtotal' => 'integer',
|
||||||
'total' => 'integer',
|
'total' => 'integer',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createdBy(): BelongsTo
|
// 3. Attribute
|
||||||
{
|
|
||||||
return $this->belongsTo(User::class, 'created_by_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function items(): HasMany
|
|
||||||
{
|
|
||||||
return $this->hasMany(PurchaseItem::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function ownerVerificationRequests(): MorphMany
|
|
||||||
{
|
|
||||||
return $this->morphMany(OwnerVerificationRequest::class, 'subject');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function pendingOwnerVerificationRequest(): MorphOne
|
|
||||||
{
|
|
||||||
return $this->morphOne(OwnerVerificationRequest::class, 'subject')
|
|
||||||
->where('status', OwnerVerificationStatus::PENDING)
|
|
||||||
->latestOfMany();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function supplier(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Supplier::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createdAtFormatted(): Attribute
|
public function createdAtFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -102,6 +78,7 @@ public function totalFormatted(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Other Methods
|
||||||
public static function mediaModuleName(): string
|
public static function mediaModuleName(): string
|
||||||
{
|
{
|
||||||
return 'purchase';
|
return 'purchase';
|
||||||
@ -111,4 +88,32 @@ public function registerMediaCollections(): void
|
|||||||
{
|
{
|
||||||
$this->addMediaCollection('photos');
|
$this->addMediaCollection('photos');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Relation
|
||||||
|
public function createdBy(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'created_by_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function items(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(PurchaseItem::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ownerVerificationRequests(): MorphMany
|
||||||
|
{
|
||||||
|
return $this->morphMany(OwnerVerificationRequest::class, 'subject');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pendingOwnerVerificationRequest(): MorphOne
|
||||||
|
{
|
||||||
|
return $this->morphOne(OwnerVerificationRequest::class, 'subject')
|
||||||
|
->where('status', OwnerVerificationStatus::PENDING)
|
||||||
|
->latestOfMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supplier(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Supplier::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,38 +15,26 @@
|
|||||||
#[Appends([
|
#[Appends([
|
||||||
'quantity_formatted',
|
'quantity_formatted',
|
||||||
'quantity_input',
|
'quantity_input',
|
||||||
'unit_price_formatted',
|
|
||||||
'subtotal_formatted',
|
'subtotal_formatted',
|
||||||
'unit_abbreviation',
|
'unit_abbreviation',
|
||||||
|
'unit_price_formatted',
|
||||||
])]
|
])]
|
||||||
class PurchaseItem extends Model
|
class PurchaseItem extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'quantity' => 'decimal:4',
|
'quantity' => 'decimal:4',
|
||||||
'unit_price' => 'integer',
|
|
||||||
'subtotal' => 'integer',
|
'subtotal' => 'integer',
|
||||||
|
'unit_price' => 'integer',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function user(): BelongsTo
|
// 3. Attribute
|
||||||
{
|
|
||||||
return $this->belongsTo(User::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function purchase(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Purchase::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function rawMaterialPrice(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(RawMaterialPrice::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function quantityFormatted(): Attribute
|
public function quantityFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -85,4 +73,20 @@ public function unitPriceFormatted(): Attribute
|
|||||||
get: fn () => 'Rp '.number_format($this->unit_price, 0, ',', '.'),
|
get: fn () => 'Rp '.number_format($this->unit_price, 0, ',', '.'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Relation
|
||||||
|
public function purchase(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Purchase::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rawMaterialPrice(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(RawMaterialPrice::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
class PushSubscription extends Model
|
class PushSubscription extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Relation
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
|
|||||||
@ -19,11 +19,13 @@
|
|||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends(['unit_label', 'unit_abbreviation'])]
|
#[Appends(['unit_abbreviation', 'unit_label'])]
|
||||||
class RawMaterial extends Model
|
class RawMaterial extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, HasPendingOwnerVerification, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, HasPendingOwnerVerification, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -32,35 +34,21 @@ protected function casts(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Scope (grouped by column, then alphabetical)
|
||||||
|
// Column Group: is_active
|
||||||
#[Scope]
|
#[Scope]
|
||||||
public function active(Builder $query): void
|
protected function active(Builder $query): void
|
||||||
{
|
{
|
||||||
$query->where('is_active', true);
|
$query->where('is_active', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
#[Scope]
|
||||||
public function inactive(Builder $query): void
|
protected function inactive(Builder $query): void
|
||||||
{
|
{
|
||||||
$query->where('is_active', false);
|
$query->where('is_active', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function prices(): HasMany
|
// 4. Attribute
|
||||||
{
|
|
||||||
return $this->hasMany(RawMaterialPrice::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function ownerVerificationRequests(): MorphMany
|
|
||||||
{
|
|
||||||
return $this->morphMany(OwnerVerificationRequest::class, 'subject');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function pendingOwnerVerificationRequest(): MorphOne
|
|
||||||
{
|
|
||||||
return $this->morphOne(OwnerVerificationRequest::class, 'subject')
|
|
||||||
->where('status', OwnerVerificationStatus::PENDING)
|
|
||||||
->latestOfMany();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function unitAbbreviation(): Attribute
|
public function unitAbbreviation(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -74,4 +62,22 @@ public function unitLabel(): Attribute
|
|||||||
get: fn () => $this->unit->label(),
|
get: fn () => $this->unit->label(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Relation
|
||||||
|
public function ownerVerificationRequests(): MorphMany
|
||||||
|
{
|
||||||
|
return $this->morphMany(OwnerVerificationRequest::class, 'subject');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pendingOwnerVerificationRequest(): MorphOne
|
||||||
|
{
|
||||||
|
return $this->morphOne(OwnerVerificationRequest::class, 'subject')
|
||||||
|
->where('status', OwnerVerificationStatus::PENDING)
|
||||||
|
->latestOfMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prices(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(RawMaterialPrice::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,11 +15,13 @@
|
|||||||
use Spatie\MediaLibrary\HasMedia;
|
use Spatie\MediaLibrary\HasMedia;
|
||||||
|
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends(['price_formatted', 'stock_formatted', 'price_input', 'stock_input'])]
|
#[Appends(['price_formatted', 'price_input', 'stock_formatted', 'stock_input'])]
|
||||||
class RawMaterialPrice extends Model implements HasMedia
|
class RawMaterialPrice extends Model implements HasMedia
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, HasModuleMedia, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, HasModuleMedia, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -28,21 +30,7 @@ protected function casts(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cuttingMaterials(): HasMany
|
// 3. Attribute
|
||||||
{
|
|
||||||
return $this->hasMany(CuttingMaterial::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function purchaseItems(): HasMany
|
|
||||||
{
|
|
||||||
return $this->hasMany(PurchaseItem::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function rawMaterial(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(RawMaterial::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function priceFormatted(): Attribute
|
public function priceFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -75,6 +63,7 @@ public function stockInput(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Other Methods
|
||||||
public static function mediaModuleName(): string
|
public static function mediaModuleName(): string
|
||||||
{
|
{
|
||||||
return 'raw-material';
|
return 'raw-material';
|
||||||
@ -84,4 +73,20 @@ public function registerMediaCollections(): void
|
|||||||
{
|
{
|
||||||
$this->addMediaCollection('images');
|
$this->addMediaCollection('images');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Relation
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,10 @@
|
|||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
class Rejection extends Model
|
class Rejection extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, InteractsWithActivityLog;
|
use HasFactory, InteractsWithActivityLog;
|
||||||
|
|
||||||
|
// 2. Relation
|
||||||
public function rejectable(): MorphTo
|
public function rejectable(): MorphTo
|
||||||
{
|
{
|
||||||
return $this->morphTo();
|
return $this->morphTo();
|
||||||
|
|||||||
@ -13,18 +13,20 @@ class StockEcerHistory extends Model
|
|||||||
|
|
||||||
public $timestamps = false;
|
public $timestamps = false;
|
||||||
|
|
||||||
|
// 1. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'quantity' => 'integer',
|
|
||||||
'stock_before' => 'integer',
|
|
||||||
'stock_ecer_before' => 'integer',
|
|
||||||
'stock_after' => 'integer',
|
|
||||||
'stock_ecer_after' => 'integer',
|
|
||||||
'created_at' => 'datetime',
|
'created_at' => 'datetime',
|
||||||
|
'quantity' => 'integer',
|
||||||
|
'stock_after' => 'integer',
|
||||||
|
'stock_before' => 'integer',
|
||||||
|
'stock_ecer_after' => 'integer',
|
||||||
|
'stock_ecer_before' => 'integer',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Relation
|
||||||
public function productVariant(): BelongsTo
|
public function productVariant(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(ProductVariant::class);
|
return $this->belongsTo(ProductVariant::class);
|
||||||
|
|||||||
@ -6,6 +6,8 @@
|
|||||||
use App\Models\Concerns\InteractsWithActivityLog;
|
use App\Models\Concerns\InteractsWithActivityLog;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Appends;
|
use Illuminate\Database\Eloquent\Attributes\Appends;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
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\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
@ -14,11 +16,13 @@
|
|||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends(['status_label', 'opname_date_formatted', 'created_at_formatted', 'created_by_name'])]
|
#[Appends(['created_at_formatted', 'created_by_name', 'opname_date_formatted', 'status_label'])]
|
||||||
class StokOpname extends Model
|
class StokOpname extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -27,35 +31,33 @@ protected function casts(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function items(): HasMany
|
// 3. Scope (grouped by column, then alphabetical)
|
||||||
|
// Column Group: status
|
||||||
|
#[Scope]
|
||||||
|
protected function draft(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->hasMany(StokOpnameItem::class);
|
$query->where('status', StokOpnameStatus::DRAFT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createdBy(): BelongsTo
|
#[Scope]
|
||||||
|
protected function pending(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'created_by_id');
|
$query->where('status', StokOpnameStatus::PENDING);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function verifiedBy(): BelongsTo
|
#[Scope]
|
||||||
|
protected function rejected(Builder $query): void
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'verified_by_id');
|
$query->where('status', StokOpnameStatus::REJECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function statusLabel(): Attribute
|
#[Scope]
|
||||||
|
protected function verified(Builder $query): void
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
$query->where('status', StokOpnameStatus::VERIFIED);
|
||||||
get: fn () => $this->status->label(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function opnameDateFormatted(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: fn () => $this->opname_date?->translatedFormat('l, d F Y'),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Attribute
|
||||||
public function createdAtFormatted(): Attribute
|
public function createdAtFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -70,18 +72,33 @@ public function createdByName(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scopeDraft($query)
|
public function opnameDateFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return $query->where('status', StokOpnameStatus::DRAFT);
|
return Attribute::make(
|
||||||
|
get: fn () => $this->opname_date?->translatedFormat('l, d F Y'),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scopePending($query)
|
public function statusLabel(): Attribute
|
||||||
{
|
{
|
||||||
return $query->where('status', StokOpnameStatus::PENDING);
|
return Attribute::make(
|
||||||
|
get: fn () => $this->status->label(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scopeVerified($query)
|
// 5. Relation
|
||||||
|
public function createdBy(): BelongsTo
|
||||||
{
|
{
|
||||||
return $query->where('status', StokOpnameStatus::VERIFIED);
|
return $this->belongsTo(User::class, 'created_by_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function items(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(StokOpnameItem::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verifiedBy(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'verified_by_id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,24 +11,27 @@
|
|||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
class StokOpnameItem extends Model
|
class StokOpnameItem extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, InteractsWithActivityLog;
|
use HasFactory, InteractsWithActivityLog;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'system_stock' => 'integer',
|
|
||||||
'physical_stock' => 'integer',
|
|
||||||
'difference' => 'integer',
|
'difference' => 'integer',
|
||||||
|
'physical_stock' => 'integer',
|
||||||
|
'system_stock' => 'integer',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Relation
|
||||||
|
public function productVariant(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ProductVariant::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function stokOpname(): BelongsTo
|
public function stokOpname(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(StokOpname::class);
|
return $this->belongsTo(StokOpname::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function productVariant(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(ProductVariant::class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,10 @@
|
|||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
class Supplier extends Model
|
class Supplier extends Model
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Relation
|
||||||
public function purchases(): HasMany
|
public function purchases(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(Purchase::class);
|
return $this->hasMany(Purchase::class);
|
||||||
|
|||||||
@ -12,22 +12,24 @@
|
|||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
class SystemConfiguration extends Model implements HasMedia
|
class SystemConfiguration extends Model implements HasMedia
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, HasModuleMedia, InteractsWithActivityLog;
|
use HasFactory, HasModuleMedia, InteractsWithActivityLog;
|
||||||
|
|
||||||
|
// 2. Other Methods
|
||||||
|
public static function instance(): self
|
||||||
|
{
|
||||||
|
return static::query()->firstOrCreate(['id' => 1]);
|
||||||
|
}
|
||||||
|
|
||||||
public static function mediaModuleName(): string
|
public static function mediaModuleName(): string
|
||||||
{
|
{
|
||||||
return 'setting';
|
return 'setting';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function instance(): self
|
|
||||||
{
|
|
||||||
return static::query()->firstOrCreate(['id' => 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function registerMediaCollections(): void
|
public function registerMediaCollections(): void
|
||||||
{
|
{
|
||||||
$this->addMediaCollection('logo')->singleFile();
|
|
||||||
$this->addMediaCollection('favicon')->singleFile();
|
$this->addMediaCollection('favicon')->singleFile();
|
||||||
$this->addMediaCollection('login_cover')->singleFile();
|
$this->addMediaCollection('login_cover')->singleFile();
|
||||||
|
$this->addMediaCollection('logo')->singleFile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,8 +24,10 @@
|
|||||||
#[Appends(['role_label'])]
|
#[Appends(['role_label'])]
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use CausesActivity, HasFactory, HasRoles, InteractsWithActivityLog, Notifiable, SoftDeletes;
|
use CausesActivity, HasFactory, HasRoles, InteractsWithActivityLog, Notifiable, SoftDeletes;
|
||||||
|
|
||||||
|
// 2. Casting
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -35,6 +37,39 @@ protected function casts(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Scope (grouped by column, then alphabetical)
|
||||||
|
// Column Group: is_active
|
||||||
|
#[Scope]
|
||||||
|
protected function active(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('is_active', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Scope]
|
||||||
|
protected function inactive(Builder $query): void
|
||||||
|
{
|
||||||
|
$query->where('is_active', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Attribute
|
||||||
|
public function roleLabel(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: function () {
|
||||||
|
$role = $this->roles->first();
|
||||||
|
|
||||||
|
if (! $role) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$enum = Role::tryFrom($role->name);
|
||||||
|
|
||||||
|
return $enum ? $enum->label() : ucwords(str_replace('-', ' ', $role->name));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Relation
|
||||||
public function cashTransactions(): HasMany
|
public function cashTransactions(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(CashTransaction::class, 'created_by_id');
|
return $this->hasMany(CashTransaction::class, 'created_by_id');
|
||||||
@ -80,6 +115,11 @@ public function marketingOrders(): HasMany
|
|||||||
return $this->hasMany(Order::class, 'marketing_id');
|
return $this->hasMany(Order::class, 'marketing_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function notifications(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Notification::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function orderItems(): HasMany
|
public function orderItems(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(OrderItem::class);
|
return $this->hasMany(OrderItem::class);
|
||||||
@ -105,11 +145,6 @@ public function purchases(): HasMany
|
|||||||
return $this->hasMany(Purchase::class, 'created_by_id');
|
return $this->hasMany(Purchase::class, 'created_by_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function notifications(): HasMany
|
|
||||||
{
|
|
||||||
return $this->hasMany(Notification::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function pushSubscriptions(): HasMany
|
public function pushSubscriptions(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(PushSubscription::class);
|
return $this->hasMany(PushSubscription::class);
|
||||||
@ -124,33 +159,4 @@ public function verifiedLeaveRequests(): HasMany
|
|||||||
{
|
{
|
||||||
return $this->hasMany(LeaveRequest::class, 'verified_by_id');
|
return $this->hasMany(LeaveRequest::class, 'verified_by_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function roleLabel(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: function () {
|
|
||||||
$role = $this->roles->first();
|
|
||||||
|
|
||||||
if (! $role) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$enum = Role::tryFrom($role->name);
|
|
||||||
|
|
||||||
return $enum ? $enum->label() : ucwords(str_replace('-', ' ', $role->name));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Scope]
|
|
||||||
public function active(Builder $query): void
|
|
||||||
{
|
|
||||||
$query->where('is_active', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Scope]
|
|
||||||
public function inactive(Builder $query): void
|
|
||||||
{
|
|
||||||
$query->where('is_active', false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,35 +19,23 @@
|
|||||||
#[Appends(['birth_date_formatted', 'birth_date_input', 'gender_label', 'profile_photo_url'])]
|
#[Appends(['birth_date_formatted', 'birth_date_input', 'gender_label', 'profile_photo_url'])]
|
||||||
class UserProfile extends Model implements HasMedia
|
class UserProfile extends Model implements HasMedia
|
||||||
{
|
{
|
||||||
|
// 1. Use Trait
|
||||||
use HasFactory, HasModuleMedia, InteractsWithActivityLog, SoftDeletes;
|
use HasFactory, HasModuleMedia, InteractsWithActivityLog, SoftDeletes;
|
||||||
|
|
||||||
public static function mediaModuleName(): string
|
// 2. Casting
|
||||||
{
|
|
||||||
return 'user-profile';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function registerMediaCollections(): void
|
|
||||||
{
|
|
||||||
$this->addMediaCollection('profile_photo')->singleFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'gender' => Gender::class,
|
|
||||||
'birth_date' => 'date',
|
'birth_date' => 'date',
|
||||||
|
'gender' => Gender::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function user(): BelongsTo
|
// 3. Attribute
|
||||||
{
|
|
||||||
return $this->belongsTo(User::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function birthDateFormatted(): Attribute
|
public function birthDateFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: fn () => Carbon::parse($this->birth_date)->translatedFormat('l, d F Y'),
|
get: fn () => $this->birth_date ? Carbon::parse($this->birth_date)->translatedFormat('l, d F Y') : '-',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +49,7 @@ public function birthDateInput(): Attribute
|
|||||||
public function genderLabel(): Attribute
|
public function genderLabel(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: fn () => $this->gender?->label(),
|
get: fn () => $this->gender?->label() ?? '-',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,4 +59,21 @@ public function profilePhotoUrl(): Attribute
|
|||||||
get: fn () => $this->getFirstMediaUrl('profile_photo') ?: null,
|
get: fn () => $this->getFirstMediaUrl('profile_photo') ?: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Other Methods
|
||||||
|
public static function mediaModuleName(): string
|
||||||
|
{
|
||||||
|
return 'user-profile';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerMediaCollections(): void
|
||||||
|
{
|
||||||
|
$this->addMediaCollection('profile_photo')->singleFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Relation
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -202,7 +202,7 @@ public function getRevenueSummary(?Carbon $startDate = null, ?Carbon $endDate =
|
|||||||
'total_revenue' => (int) ($revenueSummary->total_revenue ?? 0),
|
'total_revenue' => (int) ($revenueSummary->total_revenue ?? 0),
|
||||||
'total_discount' => (int) ($revenueSummary->total_discount ?? 0),
|
'total_discount' => (int) ($revenueSummary->total_discount ?? 0),
|
||||||
'total_marketplace_fees' => $totalMarketplaceFees,
|
'total_marketplace_fees' => $totalMarketplaceFees,
|
||||||
'total_potongan' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees,
|
'total_deduction' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees,
|
||||||
'total_orders' => (int) ($revenueSummary->total_orders ?? 0),
|
'total_orders' => (int) ($revenueSummary->total_orders ?? 0),
|
||||||
'avg_order' => (int) ($revenueSummary->avg_order ?? 0),
|
'avg_order' => (int) ($revenueSummary->avg_order ?? 0),
|
||||||
];
|
];
|
||||||
@ -259,13 +259,13 @@ public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate =
|
|||||||
$revenue = $monthlyData->firstWhere('month_key', $key);
|
$revenue = $monthlyData->firstWhere('month_key', $key);
|
||||||
$fees = $monthlyFees->get($key, 0);
|
$fees = $monthlyFees->get($key, 0);
|
||||||
$discount = (int) ($revenue->total_discount ?? 0);
|
$discount = (int) ($revenue->total_discount ?? 0);
|
||||||
$potongan = $discount + $fees;
|
$deduction = $discount + $fees;
|
||||||
|
|
||||||
$result[] = [
|
$result[] = [
|
||||||
'month' => $monthLabel,
|
'month' => $monthLabel,
|
||||||
'total' => (int) ($revenue->total_revenue ?? 0),
|
'total' => (int) ($revenue->total_revenue ?? 0),
|
||||||
'net' => (int) ($revenue->total_revenue ?? 0) - $potongan,
|
'net' => (int) ($revenue->total_revenue ?? 0) - $deduction,
|
||||||
'potongan' => $potongan,
|
'deduction' => $deduction,
|
||||||
];
|
];
|
||||||
|
|
||||||
$current->addMonth();
|
$current->addMonth();
|
||||||
@ -360,9 +360,9 @@ public function getMonthlyExpense(?Carbon $startDate = null, ?Carbon $endDate =
|
|||||||
$result[] = [
|
$result[] = [
|
||||||
'month' => $monthLabel,
|
'month' => $monthLabel,
|
||||||
'total' => $purchaseAmount + $expenseAmount + $advanceAmount,
|
'total' => $purchaseAmount + $expenseAmount + $advanceAmount,
|
||||||
'belanja' => $purchaseAmount,
|
'purchase' => $purchaseAmount,
|
||||||
'pengeluaran' => $expenseAmount,
|
'expense' => $expenseAmount,
|
||||||
'kasbon' => $advanceAmount,
|
'advance' => $advanceAmount,
|
||||||
];
|
];
|
||||||
|
|
||||||
$current->addMonth();
|
$current->addMonth();
|
||||||
|
|||||||
@ -136,7 +136,7 @@ public function getRevenueSummary(): array
|
|||||||
'total_revenue' => (int) ($revenueSummary->total_revenue ?? 0),
|
'total_revenue' => (int) ($revenueSummary->total_revenue ?? 0),
|
||||||
'total_discount' => (int) ($revenueSummary->total_discount ?? 0),
|
'total_discount' => (int) ($revenueSummary->total_discount ?? 0),
|
||||||
'total_marketplace_fees' => $totalMarketplaceFees,
|
'total_marketplace_fees' => $totalMarketplaceFees,
|
||||||
'total_potongan' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees,
|
'total_deduction' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees,
|
||||||
'total_orders' => (int) ($revenueSummary->total_orders ?? 0),
|
'total_orders' => (int) ($revenueSummary->total_orders ?? 0),
|
||||||
'avg_order' => (int) ($revenueSummary->avg_order ?? 0),
|
'avg_order' => (int) ($revenueSummary->avg_order ?? 0),
|
||||||
];
|
];
|
||||||
|
|||||||
@ -70,7 +70,7 @@ const props = defineProps<{
|
|||||||
total_revenue: number;
|
total_revenue: number;
|
||||||
total_discount: number;
|
total_discount: number;
|
||||||
total_marketplace_fees: number;
|
total_marketplace_fees: number;
|
||||||
total_potongan: number;
|
total_deduction: number;
|
||||||
total_orders: number;
|
total_orders: number;
|
||||||
avg_order: number;
|
avg_order: number;
|
||||||
};
|
};
|
||||||
@ -78,7 +78,7 @@ const props = defineProps<{
|
|||||||
month: string;
|
month: string;
|
||||||
total: number;
|
total: number;
|
||||||
net: number;
|
net: number;
|
||||||
potongan: number;
|
deduction: number;
|
||||||
}>;
|
}>;
|
||||||
expenseSummary: {
|
expenseSummary: {
|
||||||
total: number;
|
total: number;
|
||||||
@ -89,9 +89,9 @@ const props = defineProps<{
|
|||||||
monthlyExpense: Array<{
|
monthlyExpense: Array<{
|
||||||
month: string;
|
month: string;
|
||||||
total: number;
|
total: number;
|
||||||
belanja: number;
|
purchase: number;
|
||||||
pengeluaran: number;
|
expense: number;
|
||||||
kasbon: number;
|
advance: number;
|
||||||
}>;
|
}>;
|
||||||
busyHours: Array<{
|
busyHours: Array<{
|
||||||
hour: string;
|
hour: string;
|
||||||
@ -166,7 +166,7 @@ function onPresetChange(value: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Revenue Chart
|
// Revenue Chart
|
||||||
type MonthlyRevenueData = { month: string; total: number; net: number; potongan: number };
|
type MonthlyRevenueData = { month: string; total: number; net: number; deduction: number };
|
||||||
|
|
||||||
const revenueChartConfig = {
|
const revenueChartConfig = {
|
||||||
total: {
|
total: {
|
||||||
@ -177,55 +177,55 @@ const revenueChartConfig = {
|
|||||||
label: 'Bersih',
|
label: 'Bersih',
|
||||||
color: 'var(--chart-2)',
|
color: 'var(--chart-2)',
|
||||||
},
|
},
|
||||||
potongan: {
|
deduction: {
|
||||||
label: 'Potongan',
|
label: 'Potongan',
|
||||||
color: 'var(--chart-3)',
|
color: 'var(--chart-3)',
|
||||||
},
|
},
|
||||||
} satisfies ChartConfig;
|
} satisfies ChartConfig;
|
||||||
|
|
||||||
const activeRevenueChart = ref<'total' | 'net' | 'potongan'>('total');
|
const activeRevenueChart = ref<'total' | 'net' | 'deduction'>('total');
|
||||||
|
|
||||||
const revenueTotals = computed(() => ({
|
const revenueTotals = computed(() => ({
|
||||||
total: props.revenueSummary.total_revenue,
|
total: props.revenueSummary.total_revenue,
|
||||||
net: props.revenueSummary.total_revenue - props.revenueSummary.total_potongan,
|
net: props.revenueSummary.total_revenue - props.revenueSummary.total_deduction,
|
||||||
potongan: props.revenueSummary.total_potongan,
|
deduction: props.revenueSummary.total_deduction,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Expense Chart
|
// Expense Chart
|
||||||
type MonthlyExpenseData = { month: string; total: number; belanja: number; pengeluaran: number; kasbon: number };
|
type MonthlyExpenseData = { month: string; total: number; purchase: number; expense: number; advance: number };
|
||||||
|
|
||||||
const expenseChartConfig = {
|
const expenseChartConfig = {
|
||||||
total: {
|
total: {
|
||||||
label: 'Total',
|
label: 'Total',
|
||||||
color: 'var(--chart-1)',
|
color: 'var(--chart-1)',
|
||||||
},
|
},
|
||||||
belanja: {
|
purchase: {
|
||||||
label: 'Belanja',
|
label: 'Belanja',
|
||||||
color: 'var(--chart-2)',
|
color: 'var(--chart-2)',
|
||||||
},
|
},
|
||||||
pengeluaran: {
|
expense: {
|
||||||
label: 'Pengeluaran',
|
label: 'Pengeluaran',
|
||||||
color: 'var(--chart-3)',
|
color: 'var(--chart-3)',
|
||||||
},
|
},
|
||||||
kasbon: {
|
advance: {
|
||||||
label: 'Kasbon',
|
label: 'Kasbon',
|
||||||
color: 'var(--chart-4)',
|
color: 'var(--chart-4)',
|
||||||
},
|
},
|
||||||
} satisfies ChartConfig;
|
} satisfies ChartConfig;
|
||||||
|
|
||||||
const activeExpenseChart = ref<'total' | 'belanja' | 'pengeluaran' | 'kasbon'>('total');
|
const activeExpenseChart = ref<'total' | 'purchase' | 'expense' | 'advance'>('total');
|
||||||
|
|
||||||
const expenseTotals = computed(() => ({
|
const expenseTotals = computed(() => ({
|
||||||
total: props.expenseSummary.total,
|
total: props.expenseSummary.total,
|
||||||
belanja: props.expenseSummary.purchase_total,
|
purchase: props.expenseSummary.purchase_total,
|
||||||
pengeluaran: props.expenseSummary.expense_total,
|
expense: props.expenseSummary.expense_total,
|
||||||
kasbon: props.expenseSummary.advance_total,
|
advance: props.expenseSummary.advance_total,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const visibleExpenseCharts = computed(() => {
|
const visibleExpenseCharts = computed(() => {
|
||||||
const charts = ['total', 'belanja', 'pengeluaran', 'kasbon'] as const;
|
const charts = ['total', 'purchase', 'expense', 'advance'] as const;
|
||||||
if (hasRole('admin-toko')) {
|
if (hasRole('admin-toko')) {
|
||||||
return charts.filter((c) => c !== 'belanja');
|
return charts.filter((c) => c !== 'purchase');
|
||||||
}
|
}
|
||||||
return charts;
|
return charts;
|
||||||
});
|
});
|
||||||
@ -451,7 +451,7 @@ watch([startDate, endDate], () => {
|
|||||||
<CardTitle>Pendapatan</CardTitle>
|
<CardTitle>Pendapatan</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<button v-for="chart in ['total', 'net', 'potongan'] as const" :key="chart"
|
<button v-for="chart in ['total', 'net', 'deduction'] as const" :key="chart"
|
||||||
:data-active="activeRevenueChart === chart"
|
:data-active="activeRevenueChart === chart"
|
||||||
class="data-[active=true]:bg-muted/50 flex flex-1 flex-col justify-center gap-1 border-t px-6 py-4 text-left even:border-l sm:border-t-0 sm:border-l sm:px-8 sm:py-6"
|
class="data-[active=true]:bg-muted/50 flex flex-1 flex-col justify-center gap-1 border-t px-6 py-4 text-left even:border-l sm:border-t-0 sm:border-l sm:px-8 sm:py-6"
|
||||||
@click="activeRevenueChart = chart">
|
@click="activeRevenueChart = chart">
|
||||||
|
|||||||
@ -46,7 +46,7 @@ interface DashboardProps {
|
|||||||
total_revenue: number;
|
total_revenue: number;
|
||||||
total_discount: number;
|
total_discount: number;
|
||||||
total_marketplace_fees: number;
|
total_marketplace_fees: number;
|
||||||
total_potongan: number;
|
total_deduction: number;
|
||||||
total_orders: number;
|
total_orders: number;
|
||||||
avg_order: number;
|
avg_order: number;
|
||||||
};
|
};
|
||||||
@ -289,12 +289,12 @@ const marketingChartConfig = computed(() => {
|
|||||||
label: 'Bersih',
|
label: 'Bersih',
|
||||||
value: 'Rp' + formatRupiah(
|
value: 'Rp' + formatRupiah(
|
||||||
revenueSummary.total_revenue -
|
revenueSummary.total_revenue -
|
||||||
revenueSummary.total_potongan,
|
revenueSummary.total_deduction,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Potongan',
|
label: 'Potongan',
|
||||||
value: 'Rp' + formatRupiah(revenueSummary.total_potongan),
|
value: 'Rp' + formatRupiah(revenueSummary.total_deduction),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Diskon',
|
label: 'Diskon',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user