feat: integrate activity log functionality in ProductPrice model and enhance change formatting in ActivityLogService
This commit is contained in:
parent
d74d120a6c
commit
1d5863efaf
@ -3,19 +3,21 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Enums\PriceType;
|
use App\Enums\PriceType;
|
||||||
|
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\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 Spatie\Activitylog\Support\LogOptions;
|
||||||
|
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends(['price_formatted', 'price_input'])]
|
#[Appends(['price_formatted', 'price_input'])]
|
||||||
class ProductPrice extends Model
|
class ProductPrice extends Model
|
||||||
{
|
{
|
||||||
// 1. Use Trait
|
// 1. Use Trait
|
||||||
use HasFactory;
|
use HasFactory, InteractsWithActivityLog;
|
||||||
|
|
||||||
// 2. Casting
|
// 2. Casting
|
||||||
protected function casts(): array
|
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
|
// 3. Attribute
|
||||||
public function priceFormatted(): Attribute
|
public function priceFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: fn () => 'Rp '.number_format($this->price, 0, ',', '.'),
|
get: fn() => 'Rp ' . number_format($this->price, 0, ',', '.'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function priceInput(): Attribute
|
public function priceInput(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: fn () => (string) $this->price,
|
get: fn() => (string) $this->price,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Enums\ActivityEventLabel;
|
use App\Enums\ActivityEventLabel;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Support\ActivityLog\FieldLabel;
|
||||||
use App\Support\ActivityLog\ModelLabel;
|
use App\Support\ActivityLog\ModelLabel;
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
@ -62,18 +63,32 @@ private function formatChanges(?Collection $changes): array
|
|||||||
$attributes = $changes->get('attributes', []);
|
$attributes = $changes->get('attributes', []);
|
||||||
$old = $changes->get('old', []);
|
$old = $changes->get('old', []);
|
||||||
|
|
||||||
if (! is_array($attributes)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$formatted = [];
|
$formatted = [];
|
||||||
|
|
||||||
foreach ($attributes as $field => $newValue) {
|
if (is_array($attributes) && count($attributes) > 0) {
|
||||||
$formatted[] = [
|
foreach ($attributes as $field => $newValue) {
|
||||||
'field' => (string) $field,
|
if (! FieldLabel::isFormInput((string) $field)) {
|
||||||
'old' => is_array($old) ? ($old[$field] ?? null) : null,
|
continue;
|
||||||
'new' => $newValue,
|
}
|
||||||
];
|
|
||||||
|
$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;
|
return $formatted;
|
||||||
|
|||||||
297
app/Support/ActivityLog/FieldLabel.php
Normal file
297
app/Support/ActivityLog/FieldLabel.php
Normal 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -20,6 +20,7 @@
|
|||||||
use App\Models\PayrollAdjustment;
|
use App\Models\PayrollAdjustment;
|
||||||
use App\Models\PayrollPeriod;
|
use App\Models\PayrollPeriod;
|
||||||
use App\Models\Product;
|
use App\Models\Product;
|
||||||
|
use App\Models\ProductPrice;
|
||||||
use App\Models\ProductVariant;
|
use App\Models\ProductVariant;
|
||||||
use App\Models\Purchase;
|
use App\Models\Purchase;
|
||||||
use App\Models\PurchaseItem;
|
use App\Models\PurchaseItem;
|
||||||
@ -58,6 +59,7 @@ class ModelLabel
|
|||||||
PayrollAdjustment::class => 'Penyesuaian Gaji',
|
PayrollAdjustment::class => 'Penyesuaian Gaji',
|
||||||
PayrollPeriod::class => 'Periode Gaji',
|
PayrollPeriod::class => 'Periode Gaji',
|
||||||
Product::class => 'Produk',
|
Product::class => 'Produk',
|
||||||
|
ProductPrice::class => 'Harga Produk',
|
||||||
ProductVariant::class => 'Varian Produk',
|
ProductVariant::class => 'Varian Produk',
|
||||||
Purchase::class => 'Belanja',
|
Purchase::class => 'Belanja',
|
||||||
PurchaseItem::class => 'Item Belanja',
|
PurchaseItem::class => 'Item Belanja',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user