feat: integrate activity log functionality in ProductPrice model and enhance change formatting in ActivityLogService
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (8.3) (push) Waiting to run
tests / ci (8.4) (push) Waiting to run
tests / ci (8.5) (push) Waiting to run

This commit is contained in:
Yoga Pangestu 2026-07-27 23:00:22 +07:00
parent d74d120a6c
commit 1d5863efaf
4 changed files with 342 additions and 13 deletions

View File

@ -3,19 +3,21 @@
namespace App\Models;
use App\Enums\PriceType;
use App\Models\Concerns\InteractsWithActivityLog;
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 Spatie\Activitylog\Support\LogOptions;
#[Guarded(['id'])]
#[Appends(['price_formatted', 'price_input'])]
class ProductPrice extends Model
{
// 1. Use Trait
use HasFactory;
use HasFactory, InteractsWithActivityLog;
// 2. Casting
protected function casts(): array
@ -26,18 +28,31 @@ protected function casts(): array
];
}
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logUnguarded()
->logOnlyDirty()
->dontLogEmptyChanges()
->logExcept([
'id',
'variant_id',
'deleted_at',
]);
}
// 3. Attribute
public function priceFormatted(): Attribute
{
return Attribute::make(
get: fn () => 'Rp '.number_format($this->price, 0, ',', '.'),
get: fn() => 'Rp ' . number_format($this->price, 0, ',', '.'),
);
}
public function priceInput(): Attribute
{
return Attribute::make(
get: fn () => (string) $this->price,
get: fn() => (string) $this->price,
);
}

View File

@ -4,6 +4,7 @@
use App\Enums\ActivityEventLabel;
use App\Models\User;
use App\Support\ActivityLog\FieldLabel;
use App\Support\ActivityLog\ModelLabel;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
@ -62,18 +63,32 @@ private function formatChanges(?Collection $changes): array
$attributes = $changes->get('attributes', []);
$old = $changes->get('old', []);
if (! is_array($attributes)) {
return [];
}
$formatted = [];
foreach ($attributes as $field => $newValue) {
$formatted[] = [
'field' => (string) $field,
'old' => is_array($old) ? ($old[$field] ?? null) : null,
'new' => $newValue,
];
if (is_array($attributes) && count($attributes) > 0) {
foreach ($attributes as $field => $newValue) {
if (! FieldLabel::isFormInput((string) $field)) {
continue;
}
$formatted[] = [
'field' => FieldLabel::for((string) $field),
'old' => is_array($old) ? ($old[$field] ?? null) : null,
'new' => $newValue,
];
}
} elseif (is_array($old) && count($old) > 0) {
foreach ($old as $field => $oldValue) {
if (! FieldLabel::isFormInput((string) $field)) {
continue;
}
$formatted[] = [
'field' => FieldLabel::for((string) $field),
'old' => $oldValue,
'new' => null,
];
}
}
return $formatted;

View File

@ -0,0 +1,297 @@
<?php
namespace App\Support\ActivityLog;
class FieldLabel
{
/**
* @var array<string, string>
*/
private const LABELS = [
// Common
'name' => 'Nama',
'description' => 'Deskripsi',
'status' => 'Status',
'amount' => 'Jumlah',
'quantity' => 'Jumlah',
'price' => 'Harga',
'balance' => 'Saldo',
'discount' => 'Diskon',
'notes' => 'Keterangan',
'reason' => 'Alasan',
'type' => 'Jenis',
'unit' => 'Satuan',
'stock' => 'Stok',
'retail_stock' => 'Stok Ecer',
'reject_stock' => 'Stok Reject',
// Product
'category_id' => 'Kategori',
'category_ids' => 'Kategori',
'product_variant_id' => 'Varian Produk',
'variants' => 'Varian',
'prices' => 'Harga',
'is_active' => 'Status Aktif',
// Order
'customer_id' => 'Pelanggan',
'marketing_id' => 'Marketing',
'channel' => 'Channel',
'price_type' => 'Tipe Harga',
'payment_type' => 'Tipe Pembayaran',
'is_affiliate' => 'Pesanan Afiliasi',
'tiktok_order_id' => 'ID Pesanan TikTok Shop',
'shopee_order_id' => 'ID Pesanan Shopee',
'nego_price' => 'Harga Nego',
'items' => 'Item',
'subtotal' => 'Subtotal',
'total' => 'Total',
'shipping_cost' => 'Ongkir',
// Purchase
'supplier_id' => 'Supplier',
// Cutting
'materials' => 'Bahan Baku',
'results' => 'Hasil Produk',
'sewing_cost' => 'Jasa Jahit',
'other_cost' => 'Biaya Lainnya',
'material_usage' => 'Pemakaian',
'material_result' => 'Hasil',
'combination_material_result' => 'Hasil Kombinasi',
'cutting_result' => 'Hasil',
'sample' => 'Sample',
'original_outside_sample' => 'Diluar Sample',
'product_name' => 'Nama Produk',
// Employee / HR
'email' => 'Email',
'username' => 'Username',
'full_name' => 'Nama Lengkap',
'phone_number' => 'Nomor Telepon',
'gender' => 'Jenis Kelamin',
'birth_date' => 'Tanggal Lahir',
'address' => 'Alamat',
'role' => 'Role',
'join_date' => 'Tanggal Bergabung',
'employment_status' => 'Status Kepegawaian',
'base_salary' => 'Gaji Pokok',
'start_date' => 'Tanggal Mulai',
'end_date' => 'Tanggal Selesai',
'due_date' => 'Jatuh Tempo',
'opname_date' => 'Tanggal Opname',
'stock_type' => 'Tipe Stok',
'physical_stock' => 'Stok Fisik',
// Finance
'payment' => 'Pembayaran',
'paid_amount' => 'Jumlah Bayar',
// Attendance
'photo' => 'Foto',
'latitude' => 'Latitude',
'longitude' => 'Longitude',
'check_in' => 'Jam Masuk',
'check_out' => 'Jam Pulang',
// System
'app_name' => 'Nama Aplikasi',
'about_app' => 'Tentang Aplikasi',
'phone' => 'Nomor Telepon',
'logo' => 'Logo',
'favicon' => 'Favicon',
'login_cover' => 'Cover Login',
'permissions' => 'Hak Akses',
'appearance' => 'Tampilan',
'hero_image' => 'Foto Hero',
'about_image' => 'Foto Tentang Kami',
'gallery_s3_keys' => 'Koleksi Lookbook',
'profile_s3_key' => 'Foto Profil',
'profile_photo' => 'Foto Profil',
// Social media
'instagram_url' => 'Instagram',
'facebook_url' => 'Facebook',
'tiktok_url' => 'TikTok',
// HR Settings
'scheduled_check_in_time' => 'Jam Masuk Kerja',
'scheduled_check_out_time' => 'Jam Pulang Kerja',
'late_penalty_amount' => 'Denda Keterlambatan',
'absent_penalty_amount' => 'Denda Bolos',
// Marketplace fees
'tiktok_shop_platform_commission' => 'TikTok Shop - Komisi Platform',
'tiktok_shop_logistics_service_fee' => 'TikTok Shop - Biaya Logistik',
'tiktok_shop_dynamic_commission' => 'TikTok Shop - Komisi Dinamis',
'tiktok_shop_order_processing_fee' => 'TikTok Shop - Biaya Proses Pesanan',
'tiktok_shop_affiliate' => 'TikTok Shop - Komisi Affiliate',
'tiktok_shop_pre_order_service_fee' => 'TikTok Shop - Biaya Pre Order',
'shopee_admin_fee' => 'Shopee - Biaya Admin',
'shopee_program_fee' => 'Shopee - Biaya Program',
'shopee_shipping_savings' => 'Shopee - Hemat Biaya Kirim',
'shopee_premium' => 'Shopee - Premi',
'shopee_service_fee' => 'Shopee - Biaya Layanan',
'shopee_order_processing_fee' => 'Shopee - Biaya Proses Pesanan',
'shopee_ams_commission_fee' => 'Shopee - Komisi AMS',
'shopee_pre_order' => 'Shopee - Pre Order',
'shopee_live_extra' => 'Shopee - Live Extra',
// Media
's3_keys' => 'Foto',
'remove_media_ids' => 'Media yang Dihapus',
];
/**
* Field names that appear as form inputs in CRUD operations.
* Auto-generated/computed fields (slug, subtotal, total, etc.) are excluded.
*
* @var array<string, true>
*/
private const FORM_INPUT_FIELDS = [
// Common
'name' => true,
'description' => true,
'status' => true,
'amount' => true,
'quantity' => true,
'price' => true,
'balance' => true,
'discount' => true,
'notes' => true,
'reason' => true,
'type' => true,
'unit' => true,
'stock' => true,
'retail_stock' => true,
'reject_stock' => true,
// Product
'category_id' => true,
'category_ids' => true,
'product_variant_id' => true,
'variants' => true,
'prices' => true,
'is_active' => true,
// Order
'customer_id' => true,
'marketing_id' => true,
'channel' => true,
'price_type' => true,
'payment_type' => true,
'is_affiliate' => true,
'tiktok_order_id' => true,
'shopee_order_id' => true,
'nego_price' => true,
'items' => true,
'shipping_cost' => true,
// Purchase
'supplier_id' => true,
'raw_material_id' => true,
'raw_material_price_id' => true,
// Cutting
'materials' => true,
'results' => true,
'sewing_cost' => true,
'other_cost' => true,
'material_usage' => true,
'material_result' => true,
'combination_material_result' => true,
'cutting_result' => true,
'sample' => true,
'original_outside_sample' => true,
'product_name' => true,
// Employee / HR
'email' => true,
'username' => true,
'full_name' => true,
'phone_number' => true,
'gender' => true,
'birth_date' => true,
'address' => true,
'role' => true,
'join_date' => true,
'employment_status' => true,
'base_salary' => true,
'start_date' => true,
'end_date' => true,
'due_date' => true,
'opname_date' => true,
'stock_type' => true,
'physical_stock' => true,
// Finance
'payment' => true,
'paid_amount' => true,
// Attendance
'photo' => true,
'latitude' => true,
'longitude' => true,
'check_in' => true,
'check_out' => true,
// System
'app_name' => true,
'about_app' => true,
'phone' => true,
'logo' => true,
'favicon' => true,
'login_cover' => true,
'permissions' => true,
'appearance' => true,
'hero_image' => true,
'about_image' => true,
'gallery_s3_keys' => true,
'profile_s3_key' => true,
'profile_photo' => true,
'unit_price' => true,
'variant' => true,
// Social media
'instagram_url' => true,
'facebook_url' => true,
'tiktok_url' => true,
// HR Settings
'scheduled_check_in_time' => true,
'scheduled_check_out_time' => true,
'late_penalty_amount' => true,
'absent_penalty_amount' => true,
// Marketplace fees
'tiktok_shop_platform_commission' => true,
'tiktok_shop_logistics_service_fee' => true,
'tiktok_shop_dynamic_commission' => true,
'tiktok_shop_order_processing_fee' => true,
'tiktok_shop_affiliate' => true,
'tiktok_shop_pre_order_service_fee' => true,
'shopee_admin_fee' => true,
'shopee_program_fee' => true,
'shopee_shipping_savings' => true,
'shopee_premium' => true,
'shopee_service_fee' => true,
'shopee_order_processing_fee' => true,
'shopee_ams_commission_fee' => true,
'shopee_pre_order' => true,
'shopee_live_extra' => true,
// Media
's3_keys' => true,
'remove_media_ids' => true,
];
public static function for(string $field): string
{
return self::LABELS[$field] ?? ucfirst(str_replace('_', ' ', $field));
}
public static function isFormInput(string $field): bool
{
return isset(self::FORM_INPUT_FIELDS[$field]);
}
}

View File

@ -20,6 +20,7 @@
use App\Models\PayrollAdjustment;
use App\Models\PayrollPeriod;
use App\Models\Product;
use App\Models\ProductPrice;
use App\Models\ProductVariant;
use App\Models\Purchase;
use App\Models\PurchaseItem;
@ -58,6 +59,7 @@ class ModelLabel
PayrollAdjustment::class => 'Penyesuaian Gaji',
PayrollPeriod::class => 'Periode Gaji',
Product::class => 'Produk',
ProductPrice::class => 'Harga Produk',
ProductVariant::class => 'Varian Produk',
Purchase::class => 'Belanja',
PurchaseItem::class => 'Item Belanja',