From cd0995f04eaace2646cabf15f12520a104a1ac51 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 7 Aug 2026 23:04:28 +0700 Subject: [PATCH] feat: add formatted accessors for various models and enhance date formatting --- .ai/CONVENTIONS.md | 53 ++++++++++++++++++++++- .ai/REFERENCE.md | 9 ++++ app/Models/AppNotification.php | 10 +++++ app/Models/Attendance.php | 18 +++++++- app/Models/CuttingMaterial.php | 17 ++++++++ app/Models/CuttingMaterialCombination.php | 10 +++++ app/Models/CuttingResult.php | 24 ++++++++++ app/Models/EmployeeAdvancePayment.php | 2 +- app/Models/PayrollPeriod.php | 17 +++++++- app/Models/RetailStockHistory.php | 38 ++++++++++++++++ app/Models/StockMutation.php | 24 ++++++++++ 11 files changed, 217 insertions(+), 5 deletions(-) diff --git a/.ai/CONVENTIONS.md b/.ai/CONVENTIONS.md index 3df4dd3..a396fdf 100644 --- a/.ai/CONVENTIONS.md +++ b/.ai/CONVENTIONS.md @@ -258,7 +258,7 @@ ### Accessor & Mutator — Nama harus BERBEDA dari kolom DB use Illuminate\Database\Eloquent\Casts\Attribute; -// Currency → prefix 'formatted_' +// Currency → prefix 'formatted_' + 'Rp ' prefix protected function formattedAmount(): Attribute { return Attribute::make( @@ -266,7 +266,15 @@ ### Accessor & Mutator — Nama harus BERBEDA dari kolom DB ); } -// Date → prefix 'formatted_' +// Number (bukan rupiah) → prefix 'formatted_' +protected function formattedStock(): Attribute +{ + return Attribute::make( + get: fn () => number_format($this->stock, 0, ',', '.'), + ); +} + +// Date → prefix 'formatted_' + format 'l, d F Y' protected function formattedJoinDate(): Attribute { return Attribute::make( @@ -274,6 +282,14 @@ ### Accessor & Mutator — Nama harus BERBEDA dari kolom DB ); } +// DateTime → prefix 'formatted_' + format 'l, d F Y H:i' +protected function formattedCheckInAt(): Attribute +{ + return Attribute::make( + get: fn () => $this->check_in_at?->translatedFormat('l, d F Y H:i'), + ); +} + // String → prefix 'formatted_' protected function formattedName(): Attribute { @@ -293,6 +309,14 @@ ### Accessor & Mutator — Nama harus BERBEDA dari kolom DB ); } +// Month name → suffix '_name' +protected function monthName(): Attribute +{ + return Attribute::make( + get: fn () => $this->month ? Carbon::create()->month($this->month)->translatedFormat('F') : null, + ); +} + // Label → suffix '_label' (TIDAK pakai formatted_) // TIDAK PERLU accessor manual — Enum sudah punya method label() // Cukup panggil langsung: $this->status->label() @@ -303,6 +327,31 @@ ### Accessor & Mutator — Nama harus BERBEDA dari kolom DB // ✅ BENAR: formattedAmount(), formattedName() ``` +### Accessor Format Rules +```php +// ✅ Format yang WAJIB diikuti untuk accessor: + +// 1. Date (kolom date) → 'l, d F Y' +// Contoh: "Selasa, 25 Agu 2026" +$this->join_date?->translatedFormat('l, d F Y') + +// 2. DateTime (kolom datetime) → 'l, d F Y H:i' +// Contoh: "Selasa, 25 Agu 2026 12:12" +$this->check_in_at?->translatedFormat('l, d F Y H:i') + +// 3. Currency (rupiah) → 'Rp ' + number_format(0, ',', '.') +// Contoh: "Rp 1.000.000" +'Rp ' . number_format($this->amount, 0, ',', '.') + +// 4. Number (angka biasa) → number_format(0, ',', '.') +// Contoh: "1.000" +number_format($this->stock, 0, ',', '.') + +// 5. Month name → Carbon translatedFormat('F') +// Contoh: "Januari", "Februari" +Carbon::create()->month($this->month)->translatedFormat('F') +``` + ### Appends — Data yang Ditampilkan ```php // ✅ Gunakan snake_case (sama dengan nama accessor) diff --git a/.ai/REFERENCE.md b/.ai/REFERENCE.md index abfeb1a..76bf4f5 100644 --- a/.ai/REFERENCE.md +++ b/.ai/REFERENCE.md @@ -25,6 +25,7 @@ ### `push_subscriptions` → PushSubscription ### `notifications` → AppNotification `id` `user_id`(FK→users) `title` `body`(text,null) `url`(null) `is_read`(bool,default:false) `read_at`(datetime,null) `created_at` `updated_at` - Casts: is_read(bool), read_at(datetime) +- Accessor: formatted_read_at → 'l, d F Y H:i' - Relations: user(BelongsTo→User) --- @@ -113,6 +114,7 @@ ### `employee_advance_payments` → EmployeeAdvancePayment `id` `employee_advance_id`(FK→employee_advances) `paid_by_id`(FK→users,null) `cash_transaction_id`(FK→cash_transactions,unique,null) `amount`(ubig) `description`(100,null) `paid_at`(datetime) `created_at` `updated_at` - No soft deletes - Casts: amount(int), paid_at(datetime) +- Accessor: formatted_amount → 'Rp X.XXX', formatted_paid_at → 'l, d F Y H:i' - Relations: employeeAdvance(BelongsTo→EmployeeAdvance), paidBy(BelongsTo→User), cashTransaction(BelongsTo→CashTransaction) --- @@ -129,11 +131,13 @@ ### `attendances` → Attendance `id` `employee_id`(FK→employees) `attendance_date`(date) `check_in_at`(datetime) `check_out_at`(datetime,null) `check_in_latitude`(decimal(10,7),null) `check_in_longitude`(decimal(10,7),null) `check_out_latitude`(decimal(10,7),null) `check_out_longitude`(decimal(10,7),null) `work_duration_minutes`(uint,null) `created_at` `updated_at` - No soft deletes - Casts: attendance_date(date:Y-m-d), check_in_at(datetime), check_out_at(datetime), check_in/out_latitude/longitude(decimal:7), work_duration_minutes(int) +- Accessor: formatted_attendance_date → 'l, d F Y', formatted_check_in_at → 'l, d F Y H:i', formatted_check_out_at → 'l, d F Y H:i' - Relations: employee(BelongsTo→Employee), payrollAdjustments(HasMany→PayrollAdjustment) ### `payroll_periods` → PayrollPeriod `id` `closed_by_id`(FK→users,null) `year`(usmall) `month`(utiny) `status`(enum,default:open) `closed_at`(datetime,null) `created_at` `updated_at` `deleted_at` - Casts: status(PayrollPeriodStatus), year(int), month(int), closed_at(datetime) +- Accessor: formatted_closed_at → 'l, d F Y H:i', month_name → 'Januari', status_label - Scopes: closed(), open() - Relations: closedBy(BelongsTo→User), payrolls(HasMany→Payroll) @@ -188,16 +192,19 @@ ### `cuttings` → Cutting ### `cutting_material_combinations` → CuttingMaterialCombination `id` `user_id`(FK→users,null) `cutting_id`(FK→cuttings,null) `material_result`(int,null) `created_at` `updated_at` `deleted_at` - Casts: material_result(int) +- Accessor: formatted_material_result → 'X.XXX' - Relations: cutting(BelongsTo→Cutting), cuttingMaterials(HasMany→CuttingMaterial,combination_id), user(BelongsTo→User) ### `cutting_materials` → CuttingMaterial `id` `user_id`(FK→users,null) `cutting_id`(FK→cuttings,null) `raw_material_price_id`(FK→raw_material_prices) `combination_id`(FK→cutting_material_combinations,null) `material_usage`(int) `material_result`(int,null) `created_at` `updated_at` `deleted_at` - Casts: material_usage(int), material_result(int) +- Accessor: formatted_material_result → 'X.XXX', formatted_material_usage → 'X.XXX' - Relations: cutting(BelongsTo→Cutting), rawMaterialPrice(BelongsTo→RawMaterialPrice), combination(BelongsTo→CuttingMaterialCombination), user(BelongsTo→User) ### `cutting_results` → CuttingResult `id` `user_id`(FK→users,null) `cutting_id`(FK→cuttings,null) `product_name`(255,null) `cutting_result`(uint,null) `sample`(uint,null) `original_outside_sample`(uint,null) `created_at` `updated_at` `deleted_at` - Casts: cutting_result(int), sample(int), original_outside_sample(int) +- Accessor: formatted_cutting_result → 'X.XXX', formatted_original_outside_sample → 'X.XXX', formatted_sample → 'X.XXX' - Relations: cutting(BelongsTo→Cutting), user(BelongsTo→User) --- @@ -243,12 +250,14 @@ ### `retail_stock_histories` → RetailStockHistory - No soft deletes, no updated_at - $timestamps = false - Casts: quantity(int), stock_before(int), retail_stock_before(int), stock_after(int), retail_stock_after(int) +- Accessor: formatted_quantity → 'X.XXX', formatted_retail_stock_before → 'X.XXX', formatted_retail_stock_after → 'X.XXX', formatted_stock_before → 'X.XXX', formatted_stock_after → 'X.XXX' - Relations: productVariant(BelongsTo→ProductVariant), user(BelongsTo→User) ### `stock_mutations` → StockMutation `id` `user_id`(FK→users) `stockable_type`(string) `stockable_id`(ubig) `type`(string) `source_type`(string,null) `source_id`(ubig,null) `quantity`(int) `stock_before`(int) `stock_after`(int) `stock_quality`(string,null) `description`(string,null) `created_at` `updated_at` - No soft deletes - Casts: quantity(int), stock_before(int), stock_after(int) +- Accessor: formatted_quantity → 'X.XXX', formatted_stock_after → 'X.XXX', formatted_stock_before → 'X.XXX' - Relations: stockable(MorphTo), source(MorphTo), user(BelongsTo→User) --- diff --git a/app/Models/AppNotification.php b/app/Models/AppNotification.php index d43e4d0..905663b 100644 --- a/app/Models/AppNotification.php +++ b/app/Models/AppNotification.php @@ -2,11 +2,14 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Attributes\Appends; use Illuminate\Database\Eloquent\Attributes\Guarded; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +#[Appends(['formatted_read_at'])] #[Guarded(['id'])] class AppNotification extends Model { @@ -22,6 +25,13 @@ protected function casts(): array ]; } + protected function formattedReadAt(): Attribute + { + return Attribute::make( + get: fn () => $this->read_at?->translatedFormat('l, d F Y H:i'), + ); + } + public function user(): BelongsTo { return $this->belongsTo(User::class); diff --git a/app/Models/Attendance.php b/app/Models/Attendance.php index 3b242f3..581cbe1 100644 --- a/app/Models/Attendance.php +++ b/app/Models/Attendance.php @@ -3,6 +3,7 @@ namespace App\Models; use Carbon\Carbon; +use Illuminate\Database\Eloquent\Attributes\Appends; use Illuminate\Database\Eloquent\Attributes\Guarded; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -12,6 +13,7 @@ use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\InteractsWithMedia; +#[Appends(['formatted_attendance_date', 'formatted_check_in_at', 'formatted_check_out_at'])] #[Guarded(['id'])] class Attendance extends Model implements HasMedia { @@ -34,7 +36,21 @@ protected function casts(): array protected function formattedAttendanceDate(): Attribute { return Attribute::make( - get: fn ($value) => $value ? Carbon::parse($value)->translatedFormat('l, d F Y') : null, + get: fn () => $this->attendance_date?->translatedFormat('l, d F Y'), + ); + } + + protected function formattedCheckInAt(): Attribute + { + return Attribute::make( + get: fn () => $this->check_in_at?->translatedFormat('l, d F Y H:i'), + ); + } + + protected function formattedCheckOutAt(): Attribute + { + return Attribute::make( + get: fn () => $this->check_out_at?->translatedFormat('l, d F Y H:i'), ); } diff --git a/app/Models/CuttingMaterial.php b/app/Models/CuttingMaterial.php index fde35c4..67282f1 100644 --- a/app/Models/CuttingMaterial.php +++ b/app/Models/CuttingMaterial.php @@ -2,12 +2,15 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Attributes\Appends; use Illuminate\Database\Eloquent\Attributes\Guarded; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; +#[Appends(['formatted_material_result', 'formatted_material_usage'])] #[Guarded(['id'])] class CuttingMaterial extends Model { @@ -21,6 +24,20 @@ protected function casts(): array ]; } + protected function formattedMaterialResult(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->material_result, 0, ',', '.'), + ); + } + + protected function formattedMaterialUsage(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->material_usage, 0, ',', '.'), + ); + } + public function combination(): BelongsTo { return $this->belongsTo(CuttingMaterialCombination::class, 'combination_id'); diff --git a/app/Models/CuttingMaterialCombination.php b/app/Models/CuttingMaterialCombination.php index 237ce8f..68b6dc0 100644 --- a/app/Models/CuttingMaterialCombination.php +++ b/app/Models/CuttingMaterialCombination.php @@ -2,13 +2,16 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Attributes\Appends; use Illuminate\Database\Eloquent\Attributes\Guarded; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; +#[Appends(['formatted_material_result'])] #[Guarded(['id'])] class CuttingMaterialCombination extends Model { @@ -21,6 +24,13 @@ protected function casts(): array ]; } + protected function formattedMaterialResult(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->material_result, 0, ',', '.'), + ); + } + public function cutting(): BelongsTo { return $this->belongsTo(Cutting::class); diff --git a/app/Models/CuttingResult.php b/app/Models/CuttingResult.php index db5d03b..70ec2ef 100644 --- a/app/Models/CuttingResult.php +++ b/app/Models/CuttingResult.php @@ -2,12 +2,15 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Attributes\Appends; use Illuminate\Database\Eloquent\Attributes\Guarded; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; +#[Appends(['formatted_cutting_result', 'formatted_original_outside_sample', 'formatted_sample'])] #[Guarded(['id'])] class CuttingResult extends Model { @@ -22,6 +25,27 @@ protected function casts(): array ]; } + protected function formattedCuttingResult(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->cutting_result, 0, ',', '.'), + ); + } + + protected function formattedOriginalOutsideSample(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->original_outside_sample, 0, ',', '.'), + ); + } + + protected function formattedSample(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->sample, 0, ',', '.'), + ); + } + public function cutting(): BelongsTo { return $this->belongsTo(Cutting::class); diff --git a/app/Models/EmployeeAdvancePayment.php b/app/Models/EmployeeAdvancePayment.php index 94dddd0..ec5b272 100644 --- a/app/Models/EmployeeAdvancePayment.php +++ b/app/Models/EmployeeAdvancePayment.php @@ -33,7 +33,7 @@ protected function formattedAmount(): Attribute protected function formattedPaidAt(): Attribute { return Attribute::make( - get: fn () => $this->paid_at?->translatedFormat('l, d F Y'), + get: fn () => $this->paid_at?->translatedFormat('l, d F Y H:i'), ); } diff --git a/app/Models/PayrollPeriod.php b/app/Models/PayrollPeriod.php index 07ed945..bba12e3 100644 --- a/app/Models/PayrollPeriod.php +++ b/app/Models/PayrollPeriod.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Enums\PayrollPeriodStatus; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Attributes\Appends; use Illuminate\Database\Eloquent\Attributes\Guarded; use Illuminate\Database\Eloquent\Attributes\Scope; @@ -14,7 +15,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; -#[Appends(['status_label'])] +#[Appends(['formatted_closed_at', 'month_name', 'status_label'])] #[Guarded(['id'])] class PayrollPeriod extends Model { @@ -30,6 +31,20 @@ protected function casts(): array ]; } + protected function formattedClosedAt(): Attribute + { + return Attribute::make( + get: fn () => $this->closed_at?->translatedFormat('l, d F Y H:i'), + ); + } + + protected function monthName(): Attribute + { + return Attribute::make( + get: fn () => $this->month ? Carbon::create()->month($this->month)->translatedFormat('F') : null, + ); + } + protected function statusLabel(): Attribute { return Attribute::make( diff --git a/app/Models/RetailStockHistory.php b/app/Models/RetailStockHistory.php index 915317b..fd5d46c 100644 --- a/app/Models/RetailStockHistory.php +++ b/app/Models/RetailStockHistory.php @@ -2,11 +2,14 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Attributes\Appends; use Illuminate\Database\Eloquent\Attributes\Guarded; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +#[Appends(['formatted_quantity', 'formatted_retail_stock_before', 'formatted_retail_stock_after', 'formatted_stock_before', 'formatted_stock_after'])] #[Guarded(['id'])] class RetailStockHistory extends Model { @@ -25,6 +28,41 @@ protected function casts(): array ]; } + protected function formattedQuantity(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->quantity, 0, ',', '.'), + ); + } + + protected function formattedRetailStockBefore(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->retail_stock_before, 0, ',', '.'), + ); + } + + protected function formattedRetailStockAfter(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->retail_stock_after, 0, ',', '.'), + ); + } + + protected function formattedStockBefore(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->stock_before, 0, ',', '.'), + ); + } + + protected function formattedStockAfter(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->stock_after, 0, ',', '.'), + ); + } + public function productVariant(): BelongsTo { return $this->belongsTo(ProductVariant::class); diff --git a/app/Models/StockMutation.php b/app/Models/StockMutation.php index 7711bf3..89f122a 100644 --- a/app/Models/StockMutation.php +++ b/app/Models/StockMutation.php @@ -2,12 +2,15 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Attributes\Appends; use Illuminate\Database\Eloquent\Attributes\Guarded; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; +#[Appends(['formatted_quantity', 'formatted_stock_after', 'formatted_stock_before'])] #[Guarded(['id'])] class StockMutation extends Model { @@ -22,6 +25,27 @@ protected function casts(): array ]; } + protected function formattedQuantity(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->quantity, 0, ',', '.'), + ); + } + + protected function formattedStockAfter(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->stock_after, 0, ',', '.'), + ); + } + + protected function formattedStockBefore(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->stock_before, 0, ',', '.'), + ); + } + public function source(): MorphTo { return $this->morphTo();