dstpabuaran.com/AGENTS.md
Yoga Pangestu 4517eeb7ee feat: enhance employee and leave request management
- Added roles property to Employee type and a new column for displaying employee roles in the employee table.
- Updated leave request columns to use formatted start and end dates instead of raw date strings.
- Enhanced leave request index to accept filter options for status dynamically.
- Refactored transaction index to support dynamic filter options for status, channel, and payment type.
- Introduced AGENTS.md for session notes detailing model relationships, casting, scopes, reorganizations, and conventions.
2026-08-04 23:03:56 +07:00

35 KiB

AGENTS.md - Session Notes

Status: In Progress


1. Relasi Dua Arah - Perubahan di Sesi Ini

Model Relasi Baru Tipe Inverse
Cutting submittedBy() belongsTo(User, 'submitted_by_id') User.submittedCuttings()
CashTransaction employeeAdvances() hasMany(EmployeeAdvance) EmployeeAdvance.cashTransaction()
CashTransaction payrolls() hasMany(Payroll) Payroll.cashTransaction()
Attendance payrollAdjustments() hasMany(PayrollAdjustment) PayrollAdjustment.attendance()
User cuttingMaterials() hasMany(CuttingMaterial) CuttingMaterial.user()
User cuttingMaterialCombinations() hasMany(CuttingMaterialCombination) CuttingMaterialCombination.user()
User cuttingResults() hasMany(CuttingResult) CuttingResult.user()
User pushSubscriptions() hasMany(PushSubscription) PushSubscription.user()

File diubah untuk relasi:

  • app/Models/Cutting.php - tambah submittedBy()
  • app/Models/CashTransaction.php - tambah employeeAdvances(), payrolls(), import HasMany
  • app/Models/Attendance.php - tambah payrollAdjustments(), import HasMany
  • app/Models/User.php - tambah 4 relasi

2. Casting - Perubahan di Sesi Ini

Model Kolom Cast Keterangan
Cutting sewing_cost integer tambah ke casts existing
Cutting other_cost integer tambah ke casts existing
OwnerVerificationRequest payload array tambah ke casts existing
Order marketplace_settings_snapshot array tambah ke casts existing
ProductVariant stock integer casts() baru dibuat
ProductVariant reject_stock integer casts() baru dibuat
ProductVariant retail_stock integer casts() baru dibuat
PayrollPeriod year integer tambah ke casts existing
PayrollPeriod month integer tambah ke casts existing

File diubah untuk casting:

  • app/Models/Cutting.php
  • app/Models/OwnerVerificationRequest.php
  • app/Models/Order.php
  • app/Models/ProductVariant.php
  • app/Models/PayrollPeriod.php

3. Scope (Ordered by Abjad) - Perubahan di Sesi Ini

Model yang DITAMBAH scope-nya:

Model Scope Baru Enum
Cutting cancelled(), completed(), inProgress() CuttingStatus
RawMaterial kg(), meter(), yard() RawMaterialUnit

Model yang SUDAH LENGKAP scopes:

Model Scopes (abjad)
CashTransaction deposit, expenseType, transfer, withdrawal
Employee contract, fullTime, internship, partTime, resigned
EmployeeAdvance approved, cancelled, paid, pending, rejected
LeaveRequest approved, cancelled, pending, rejected
Order cancelled, cash, completed, pending, processing, qris, refunded, retail, shopee, store, tiktok, transfer, wholesale
OrderItem good, reject
Payroll cancelled, paid, unpaid
PayrollAdjustment bonus, deduction
PayrollPeriod closed, open
Product active, draft, inactive
ProductPrice retail, wholesale
Restock good, reject
StokOpname cancelled, completed, draft, inProgress, verified
StokOpnameItem good, reject
UserProfile female, hasPhoneNumber, male
User active

Model yang TIDAK punya enum (tidak perlu scope):

AppNotification, CashAccount, Category, Customer, Expense, CuttingMaterial, CuttingMaterialCombination, CuttingResult, OwnerVerificationRequest, ProductCategory, ProductVariant, Purchase, PurchaseItem, RawMaterialPrice, Rejection, RestockItem, RetailStockHistory, StockMutation, Supplier, SystemConfiguration, HomepageConfiguration, PushSubscription


4. Reorganisasi Model - Semua model diubah urutannya menjadi:

casts → scopes (abjad) → relations (abjad)

Semua 41 model sudah di-reorganize di sesi ini.


5. Relasi Dua Arah - Perubahan di Sesi Ini

AppNotification

  • belongsTo User (user_id)

Attendance

  • belongsTo Employee (employee_id)
  • hasMany PayrollAdjustment

CashAccount

  • belongsTo User (created_by_id)
  • hasMany CashTransaction

CashTransaction

  • belongsTo CashAccount (cash_account_id)
  • belongsTo User (created_by_id)
  • hasMany EmployeeAdvance
  • hasMany Payroll
  • hasOne Expense
  • hasOne Order
  • morphTo Reference (reference_id, reference_type)

Category

  • belongsToMany Product (via product_categories)

Cutting

  • belongsTo User (created_by_id)
  • belongsTo User (submitted_by_id)
  • hasMany CuttingMaterialCombination
  • hasMany CuttingMaterial
  • hasMany CuttingResult

CuttingMaterial

  • belongsTo CuttingMaterialCombination (combination_id)
  • belongsTo Cutting (cutting_id)
  • belongsTo RawMaterialPrice (raw_material_price_id)
  • belongsTo User (user_id)

CuttingMaterialCombination

  • belongsTo Cutting (cutting_id)
  • hasMany CuttingMaterial (combination_id)
  • belongsTo User (user_id)

CuttingResult

  • belongsTo Cutting (cutting_id)
  • belongsTo User (user_id)

Customer

  • hasMany Order (customer_id)

Employee

  • belongsTo User (user_id)
  • hasMany Attendance
  • hasMany EmployeeAdvance
  • hasMany LeaveRequest
  • hasMany Payroll

EmployeeAdvance

  • belongsTo CashTransaction (cash_transaction_id)
  • belongsTo Employee (employee_id)
  • belongsTo User (paid_by_id)
  • belongsTo CashTransaction (repayment_cash_transaction_id)
  • belongsTo User (verified_by_id)

Expense

  • belongsTo CashTransaction (cash_transaction_id)
  • belongsTo User (created_by_id)

LeaveRequest

  • belongsTo Employee (employee_id)
  • belongsTo User (verified_by_id)

Order

  • belongsTo CashTransaction (cash_transaction_id)
  • belongsTo User (created_by_id)
  • belongsTo Customer (customer_id)
  • belongsTo User (marketing_id)
  • hasMany OrderItem (order_id)

OrderItem

  • belongsTo Order (order_id)
  • belongsTo ProductVariant (product_variant_id)
  • belongsTo User (user_id)

OwnerVerificationRequest

  • morphTo Subject (subject_id, subject_type)
  • belongsTo User (submitted_by_id)
  • belongsTo User (verified_by_id)

Payroll

  • belongsTo CashTransaction (cash_transaction_id)
  • belongsTo Employee (employee_id)
  • belongsTo User (paid_by_id)
  • hasMany PayrollAdjustment (payroll_id)
  • belongsTo PayrollPeriod (payroll_period_id)

PayrollAdjustment

  • belongsTo Attendance (attendance_id)
  • belongsTo User (created_by_id)
  • belongsTo Payroll (payroll_id)

PayrollPeriod

  • belongsTo User (closed_by_id)
  • hasMany Payroll (payroll_period_id)

Product

  • belongsToMany Category (via product_categories)
  • hasMany ProductVariant (product_id)

ProductCategory (Pivot)

  • belongsTo Category (category_id)
  • belongsTo Product (product_id)

ProductPrice

  • belongsTo ProductVariant (variant_id)

ProductVariant

  • belongsTo Product (product_id)
  • hasMany OrderItem (product_variant_id)
  • hasMany ProductPrice (variant_id)
  • hasMany RestockItem (product_variant_id)
  • hasMany RetailStockHistory (product_variant_id)
  • hasMany StokOpnameItem (product_variant_id)
  • hasMany StockMutation (stockable_id, polymorphic)

Purchase

  • belongsTo User (created_by_id)
  • hasMany PurchaseItem (purchase_id)
  • belongsTo Supplier (supplier_id)

PurchaseItem

  • belongsTo Purchase (purchase_id)
  • belongsTo RawMaterialPrice (raw_material_price_id)
  • belongsTo User (user_id)

PushSubscription

  • belongsTo User (user_id)

RawMaterial

  • hasMany RawMaterialPrice (raw_material_id)

RawMaterialPrice

  • belongsTo RawMaterial (raw_material_id)
  • hasMany CuttingMaterial (raw_material_price_id)
  • hasMany PurchaseItem (raw_material_price_id)

Rejection

  • morphTo Rejectable (rejectable_id, rejectable_type)
  • belongsTo User (rejected_by_id)

Restock

  • belongsTo User (created_by_id)
  • hasMany RestockItem (restock_id)

RestockItem

  • belongsTo ProductVariant (product_variant_id)
  • belongsTo Restock (restock_id)
  • belongsTo User (user_id)

RetailStockHistory

  • belongsTo ProductVariant (product_variant_id)
  • belongsTo User (user_id)

StockMutation

  • morphTo Source (source_id, source_type)
  • morphTo Stockable (stockable_id, stockable_type)
  • belongsTo User (user_id)

StokOpname

  • belongsTo User (created_by_id)
  • hasMany StokOpnameItem (stok_opname_id)
  • belongsTo User (verified_by_id)

StokOpnameItem

  • belongsTo ProductVariant (product_variant_id)
  • belongsTo StokOpname (stok_opname_id)

Supplier

  • hasMany Purchase (supplier_id)

User

  • hasMany Attendance (user_id)
  • hasMany CashAccount (created_by_id)
  • hasMany CashTransaction (created_by_id)
  • hasMany Cutting (created_by_id)
  • hasMany Cutting (submitted_by_id)
  • hasMany CuttingMaterialCombination (user_id)
  • hasMany CuttingMaterial (user_id)
  • hasMany CuttingResult (user_id)
  • hasMany Expense (created_by_id)
  • hasMany Order (created_by_id)
  • hasMany Order (marketing_id)
  • hasMany OrderItem (user_id)
  • hasMany OwnerVerificationRequest (submitted_by_id)
  • hasMany OwnerVerificationRequest (verified_by_id)
  • hasMany Payroll (paid_by_id)
  • hasMany PayrollAdjustment (created_by_id)
  • hasMany PayrollPeriod (closed_by_id)
  • hasMany Purchase (created_by_id)
  • hasMany PurchaseItem (user_id)
  • hasMany PushSubscription (user_id)
  • hasMany Rejection (rejected_by_id)
  • hasMany Restock (created_by_id)
  • hasMany RestockItem (user_id)
  • hasMany RetailStockHistory (user_id)
  • hasMany StockMutation (user_id)
  • hasMany StokOpname (created_by_id)
  • hasMany StokOpname (verified_by_id)
  • hasOne Employee (user_id)
  • hasOne UserProfile (user_id)

UserProfile

  • belongsTo User (user_id)

6. Complete Casting Map

Attendance

  • attendance_date -> date:Y-m-d, check_in_at -> datetime, check_out_at -> datetime
  • check_in_latitude -> decimal:7, check_in_longitude -> decimal:7
  • check_out_latitude -> decimal:7, check_out_longitude -> decimal:7
  • work_duration_minutes -> integer

CashAccount

  • balance -> integer

CashTransaction

  • type -> CashTransactionType enum, amount -> integer, balance_after -> integer

Cutting

  • status -> CuttingStatus enum, total_material_cost -> integer, cost_per_unit -> integer
  • sewing_cost -> integer, other_cost -> integer

CuttingMaterial

  • material_usage -> integer, material_result -> integer

CuttingMaterialCombination

  • material_result -> integer

CuttingResult

  • cutting_result -> integer, sample -> integer, original_outside_sample -> integer

Employee

  • employment_status -> EmploymentStatus enum, base_salary -> integer
  • join_date -> date:Y-m-d, resign_date -> date:Y-m-d

EmployeeAdvance

  • status -> EmployeeAdvanceStatus enum, amount -> integer, paid_amount -> integer
  • due_date -> date:Y-m-d, verified_at -> datetime, paid_at -> datetime

Order

  • channel -> OrderChannel enum, price_type -> PriceType enum
  • status -> OrderStatus enum, payment_type -> PaymentType enum
  • is_affiliate -> boolean, subtotal -> integer, discount -> integer
  • nego_price -> integer, total_amount -> integer, cogs -> integer
  • marketplace_settings_snapshot -> array

OrderItem

  • stock_quality -> ProductStockQuality enum, quantity -> integer
  • unit_price -> integer, subtotal -> integer

OwnerVerificationRequest

  • payload -> array, verified_at -> datetime

Payroll

  • status -> PayrollStatus enum, base_salary -> integer
  • bonus_amount -> integer, deduction_amount -> integer
  • total_amount -> integer, paid_at -> datetime

PayrollAdjustment

  • type -> PayrollAdjustmentType enum, amount -> integer

PayrollPeriod

  • status -> PayrollPeriodStatus enum, year -> integer
  • month -> integer, closed_at -> datetime

Product

  • status -> ProductStatus enum

ProductPrice

  • type -> PriceType enum, price -> integer

ProductVariant

  • stock -> integer, reject_stock -> integer, retail_stock -> integer

RawMaterial

  • unit -> RawMaterialUnit enum, is_active -> boolean

RawMaterialPrice

  • price -> integer, stock -> integer

User

  • email_verified_at -> datetime, is_active -> boolean
  • last_login_at -> datetime, password -> hashed
  • two_factor_confirmed_at -> datetime

5. Migration Foreign Keys

Table FK Column References
user_profiles user_id users(id)
employees user_id users(id)
product_categories product_id products(id)
product_categories category_id categories(id)
product_variants product_id products(id)
raw_material_prices raw_material_id raw_materials(id)
cash_accounts created_by_id users(id)
cash_transactions cash_account_id cash_accounts(id)
cash_transactions created_by_id users(id)
expenses cash_transaction_id cash_transactions(id)
expenses created_by_id users(id)
employee_advances paid_by_id users(id)
employee_advances employee_id employees(id)
employee_advances verified_by_id users(id)
employee_advances repayment_cash_transaction_id cash_transactions(id)
employee_advances cash_transaction_id cash_transactions(id)
rejections rejected_by_id users(id)
attendances employee_id employees(id)
payroll_periods closed_by_id users(id)
payrolls payroll_period_id payroll_periods(id)
payrolls employee_id employees(id)
payrolls cash_transaction_id cash_transactions(id)
payrolls paid_by_id users(id)
payroll_adjustments payroll_id payrolls(id)
payroll_adjustments attendance_id attendances(id)
payroll_adjustments created_by_id users(id)
leave_requests employee_id employees(id)
leave_requests verified_by_id users(id)
purchases supplier_id suppliers(id)
purchases created_by_id users(id)
purchase_items purchase_id purchases(id)
purchase_items user_id users(id)
purchase_items raw_material_price_id raw_material_prices(id)
orders customer_id customers(id)
orders marketing_id users(id)
orders cash_transaction_id cash_transactions(id)
orders created_by_id users(id)
order_items order_id orders(id)
order_items user_id users(id)
order_items product_variant_id product_variants(id)
cuttings created_by_id users(id)
cuttings submitted_by_id users(id)
cutting_material_combinations user_id users(id)
cutting_material_combinations cutting_id cuttings(id)
cutting_materials user_id users(id)
cutting_materials cutting_id cuttings(id)
cutting_materials raw_material_price_id raw_material_prices(id)
cutting_materials combination_id cutting_material_combinations(id)
cutting_results user_id users(id)
cutting_results cutting_id cuttings(id)
product_prices variant_id product_variants(id)
owner_verification_requests submitted_by_id users(id)
owner_verification_requests verified_by_id users(id)
notifications user_id users(id)
employee_advance_payments employee_advance_id employee_advances(id)
employee_advance_payments paid_by_id users(id)
employee_advance_payments cash_transaction_id cash_transactions(id)
retail_stock_histories product_variant_id product_variants(id)
retail_stock_histories user_id users(id)
stok_opnames created_by_id users(id)
stok_opnames verified_by_id users(id)
stok_opname_items stok_opname_id stok_opnames(id)
stok_opname_items product_variant_id product_variants(id)
restocks created_by_id users(id)
restock_items restock_id restocks(id)
restock_items user_id users(id)
restock_items product_variant_id product_variants(id)
stock_mutations user_id users(id)

8. Accessor & Mutator - Perubahan di Sesi Ini

A. Currency Format (Get: Rp X.XXX)

Model Kolom
CashAccount balance
CashTransaction amount
Cutting total_material_cost, cost_per_unit, sewing_cost, other_cost
Employee base_salary
EmployeeAdvance amount, paid_amount
Expense amount
Order subtotal, discount, nego_price, total_amount, cogs
OrderItem unit_price, subtotal
Payroll base_salary, bonus_amount, deduction_amount, total_amount
PayrollAdjustment amount
ProductPrice price
Purchase subtotal, discount, shipping_cost, total
PurchaseItem unit_price, subtotal
RawMaterialPrice price
Restock subtotal, total
RestockItem unit_price, subtotal

B. Phone Format 0821 2121 2121 (Get/Set)

Model Kolom
UserProfile phone_number
Customer phone_number
Supplier phone_number

C. String Format (Get: ucfirst)

Model Kolom
CashAccount name
Category name
Customer name
Product name
ProductVariant name
RawMaterial name
Supplier name
UserProfile first_name, last_name

D. Full Name (Computed)

Model Kolom Get
UserProfile full_name first_name . ' ' . last_name
User fullName userProfile full_name atau username

E. Email (Set: lowercase)

Model Kolom Set
User email strtolower

F. Date Format Indonesia (Get: l, d F Y)

Model Kolom
Attendance attendance_date
Employee join_date, resign_date
EmployeeAdvance due_date
LeaveRequest start_date, end_date
StokOpname opname_date
UserProfile birth_date

G. Status Label (Get: ->label())

Model Kolom
CashTransaction typeLabel (type)
Cutting statusLabel (status)
Employee employmentStatusLabel (employment_status)
EmployeeAdvance statusLabel (status)
LeaveRequest statusLabel (status)
Order statusLabel (status), channelLabel (channel), paymentTypeLabel (payment_type)
OrderItem stockQualityLabel (stock_quality)
Payroll statusLabel (status)
PayrollAdjustment typeLabel (type)
PayrollPeriod statusLabel (status)
Product statusLabel (status)
ProductPrice typeLabel (type)
RawMaterial unitLabel (unit)
Restock stockTypeLabel (stock_type)
StokOpname statusLabel (status)
StokOpnameItem stockQualityLabel (stock_quality)
UserProfile genderLabel (gender)

9. Enum Label - Perubahan di Sesi Ini

Enum yang ditambah label() method:

Enum Labels
CashTransactionType Setoran, Pengeluaran, Transfer, Penarikan
CuttingStatus Dibatalkan, Selesai, Dalam Proses
EmployeeAdvanceStatus Disetujui, Dibatalkan, Dibayar, Menunggu, Ditolak
EmploymentStatus Kontrak, Penuh Waktu, Magang, Paruh Waktu, Keluar
Gender Perempuan, Laki-laki
LeaveRequestStatus Disetujui, Dibatalkan, Menunggu, Ditolak
PayrollAdjustmentType Bonus, Potongan
PayrollPeriodStatus Tutup, Buka
PayrollStatus Dibatalkan, Dibayar, Belum Dibayar
RawMaterialUnit Kg, Meter, Yard
StokOpnameStatus Dibatalkan, Selesai, Draft, Dalam Proses, Terverifikasi

Enum yang SUDAH punya label():

OrderChannel, OrderStatus, PaymentType, PriceType, ProductStatus, ProductStockQuality

HasValues Trait - toSelect() Method

Semua enum yang pakai HasValues trait bisa panggil EnumName::toSelect() untuk return Collection<int, array{value: string, label: string}>.

// Sebelum ( verbose ):
collect(OrderStatus::cases())->map(fn ($s) => ['value' => $s->value, 'label' => $s->label()])->values()

// Sesudah ( clean ):
OrderStatus::toSelect()

// Dengan filter:
PriceType::toSelect()->filter(fn ($p) => $p['value'] !== PriceType::CAPITAL->value)->values()

File yang sudah di-refactor:

  • app/Http/Controllers/Admin/Finance/CashAccountController.php - CashTransactionType::toSelect()
  • app/Http/Controllers/Admin/HR/LeaveRequestController.php - LeaveRequestStatus::toSelect()
  • app/Services/Admin/Manage/TransactionService.php - OrderStatus::toSelect(), OrderChannel::toSelect(), PaymentType::toSelect(), PriceType::toSelect()

10. Appends - Perubahan di Sesi Ini

Catatan Penting:

  • Accessor tidak boleh sama nama dengan kolom database → gunakan prefix formatted_ untuk menghindari bentrok
  • Label accessors (status_label, type_label, dll) tidak bentrok karena bukan kolom DB

Model yang ditambah #[Appends([...])]:

Model Appends
CashAccount ['formatted_balance', 'formatted_name']
CashTransaction ['formatted_amount', 'formatted_balance_after', 'formatted_created_at', 'type_label']
Category ['formatted_name']
Cutting ['formatted_cost_per_unit', 'formatted_other_cost', 'formatted_sewing_cost', 'status_label', 'formatted_total_material_cost']
Customer ['formatted_name', 'formatted_phone_number']
Employee ['formatted_base_salary', 'employment_status_label', 'formatted_join_date', 'formatted_resign_date']
EmployeeAdvance ['formatted_amount', 'formatted_created_at', 'formatted_due_date', 'formatted_paid_amount', 'status_label']
Expense ['formatted_amount', 'formatted_created_at', 'formatted_date']
LeaveRequest ['formatted_end_date', 'formatted_start_date', 'status_label']
Order ['channel_label', 'formatted_cogs', 'formatted_discount', 'formatted_nego_price', 'payment_type_label', 'status_label', 'formatted_subtotal', 'formatted_total_amount']
OrderItem ['stock_quality_label', 'formatted_subtotal', 'formatted_unit_price']
Payroll ['formatted_base_salary', 'formatted_bonus_amount', 'formatted_deduction_amount', 'status_label', 'formatted_total_amount']
PayrollAdjustment ['formatted_amount', 'type_label']
PayrollPeriod ['status_label']
Product ['formatted_name', 'status_label']
ProductPrice ['formatted_price', 'type_label']
ProductVariant ['formatted_name']
Purchase ['formatted_discount', 'formatted_shipping_cost', 'formatted_subtotal', 'formatted_total']
PurchaseItem ['formatted_subtotal', 'formatted_unit_price']
RawMaterial ['formatted_name', 'unit_label']
RawMaterialPrice ['formatted_price']
Restock ['stock_type_label', 'formatted_subtotal', 'formatted_total']
RestockItem ['formatted_subtotal', 'formatted_unit_price']
StokOpname ['formatted_opname_date', 'status_label']
StokOpnameItem ['stock_quality_label']
Supplier ['formatted_name', 'formatted_phone_number']
UserProfile ['formatted_birth_date', 'gender_label', 'formatted_phone_number']
User ['full_name', 'name']

Yang TIDAK di-append:

  • User.email - set-only accessor (tidak ada get)

Aturan Penamaan Accessor:

Tipe Lama (BENTROK) Baru (AMAN)
Currency amount(), price(), subtotal() formattedAmount(), formattedPrice(), formattedSubtotal()
Date joinDate(), dueDate() formattedJoinDate(), formattedDueDate()
String name() formattedName()
Phone phoneNumber() formattedPhoneNumber()
Label statusLabel(), typeLabel() TIDAK berubah (tidak bentrok)

Aturan Accessor Pattern:

  • Gunakan $this-> untuk mengakses attribute, bukan parameter $value
  • Contoh: get: fn () => 'Rp ' . number_format($this->amount, 0, ',', '.')
  • Null-safe: get: fn () => $this->join_date?->translatedFormat('l, d F Y')

11. Controller Conventions - Perubahan di Sesi Ini

Base Controller (Controller.php)

  • handleAction(callable $action, string $successMessage, string $redirectRoute, ?string $errorRoute = null, array $parameters = []): RedirectResponse
  • handleToggle(callable $action, string $successMessage, string $redirectRoute): RedirectResponse

Aturan Penamaan Controller:

Pattern Keterangan
handleAction() Untuk 2+ query/operasi dalam 1 action (try-catch)
handleToggle() Untuk toggle operations (simple, non-dynamic message)
Manual flash Untuk 1 query/operasi (single query)

Controller Property:

  • Service properties harus private readonly XService $service

Method Ordering:

  1. __construct
  2. index
  3. show (jika ada)
  4. create
  5. store
  6. edit
  7. update
  8. destroy
  9. Custom actions (toggleStatus, approve, reject, dll)

Perubahan di Sesi Ini:

Controller Perubahan
StockMutationController Fix inline instantiation → private readonly StockMutationService $service
ProfileController Manual flash → handleAction() (2 queries: user save + profile updateOrCreate)
23 controllers Tambah readonly ke service properties

Catatan Penting:

  • handleAction() hanya untuk 2+ query/operasi dalam 1 action
  • 1 query = manual flash (lebih simpel, tidak perlu try-catch overhead)
  • Contoh 2+ query: ProfileController::update() → user save + profile updateOrCreate

12. Service Conventions - Perubahan di Sesi Ini

Traits (Shared Concerns)

Trait Methods Digunakan Oleh
HandlesCashTransactions getCashAccount(), creditCash(), debitCash() CashAccountService, ExpenseService, EmployeeAdvanceService, PayrollPeriodService
HasStockAdjustment adjustStock(), adjustVariantStock(), applyStock(), reverseStock() TransactionService, RestockService
RegistersMedia registerMedia(), syncPhoto() CashAccountService, CuttingService, ExpenseService, PurchaseService, RestockService, TransactionService

Aturan Penamaan Service:

Pattern Keterangan
private readonly XService $service Service property harus readonly
= new XService dilarang Gunakan dependency injection, bukan default value
getAll(array $filters = []): Collection Standar method listing
paginated(int $perPage, string $search, string $sort, string $direction, array $filters): LengthAwarePaginator Standar method pagination

Service Method Signatures:

// Standard - dengan filter
getAll(array $filters = []): Collection
paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator

// Standard - tanpa filter (tetap terima $filters untuk konsistensi)
getAll(): Collection
paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator

Cash Transaction Patterns:

  • Credit (DEPOSIT): $this->creditCash(amount, description) → tambah saldo
  • Debit (EXPENSE/WITHDRAWAL): $this->debitCash(amount, description) → kurangi saldo + validasi

Stock Adjustment Patterns:

  • ProductVariant: $this->applyStock(items, stockType, sign) → increment/decrement stock/reject_stock
  • RawMaterialPrice: $this->adjustStock(model, field, quantity, sign) → increment/decrement

Service Files Diubah di Sesi Ini:

  • app/Services/Admin/Finance/CashAccountService.php - hapus = new, tambah HandlesCashTransactions
  • app/Services/Admin/Finance/ExpenseService.php - hapus = new, tambah HandlesCashTransactions
  • app/Services/Admin/Finance/EmployeeAdvanceService.php - tambah HandlesCashTransactions
  • app/Services/Admin/Finance/PayrollPeriodService.php - tambah HandlesCashTransactions
  • app/Services/Admin/Manage/TransactionService.php - hapus = new, tambah HasStockAdjustment
  • app/Services/Admin/Manage/RestockService.php - hapus = new, tambah HasStockAdjustment
  • app/Services/Admin/Manage/CuttingService.php - hapus = new, hapus syncCuttingPhoto
  • app/Services/Admin/Manage/PurchaseService.php - hapus = new, hapus syncPurchasePhoto
  • app/Services/Admin/Master/Product/ProductService.php - hapus = new
  • app/Services/Admin/Master/Product/ProductVariantService.php - hapus = new
  • app/Services/Admin/Master/RawMaterial/RawMaterialService.php - hapus = new
  • app/Services/Admin/Master/RawMaterial/RawMaterialVariantService.php - hapus = new
  • app/Services/Admin/AdminSettingsService.php - hapus = new
  • app/Services/Admin/Settings/RoleService.php - tambah $filters ke paginated()
  • app/Services/Admin/Master/CategoryService.php - tambah $filters
  • app/Services/Admin/Master/CustomerService.php - tambah $filters
  • app/Services/Admin/Master/SupplierService.php - tambah $filters

Trait Files Baru:

  • app/Services/Concerns/HandlesCashTransactions.php
  • app/Services/Concerns/HasStockAdjustment.php

13. Form Request Conventions - Perubahan di Sesi Ini

Traits (Shared Concerns)

Trait Methods Digunakan Oleh
CurrencyStripping stripCurrencyDot(array $data, string ...$fields): array 11 Form Requests

Aturan Form Request:

Pattern Keterangan
authorize() wajib ada Selalu return true (authorization di controller/middleware)
attributes() dalam Bahasa Indonesia Label untuk semua field
prepareForValidation() Strip dot currency via CurrencyStripping trait
Shared Store/Update Gunakan 1 request dengan sometimes + if/else
Unique ignore Rule::unique('table')->ignore($this->route('model')?->id)

Currency Stripping Pattern:

use App\Concerns\CurrencyStripping;

class SomeRequest extends FormRequest
{
    use CurrencyStripping;

    public function prepareForValidation(): void
    {
        $this->merge($this->stripCurrencyDot($this->validated(), 'amount', 'discount', 'variants.*.price'));
    }
}

Form Request Files Diubah di Sesi Ini:

Tambah authorize():

  • app/Http/Requests/Settings/ProfileUpdateRequest.php
  • app/Http/Requests/Settings/ProfileDeleteRequest.php
  • app/Http/Requests/Settings/TwoFactorAuthenticationRequest.php
  • app/Http/Requests/Settings/PasswordUpdateRequest.php

Tambah CurrencyStripping trait:

  • app/Http/Requests/Admin/Manage/TransactionRequest.php - discount, nego_price
  • app/Http/Requests/Admin/Manage/PurchaseRequest.php - variants.*.price, discount, shipping_cost
  • app/Http/Requests/Admin/Master/RawMaterial/RawMaterialRequest.php - variants.*.price
  • app/Http/Requests/Admin/Master/RawMaterial/RawMaterialVariantRequest.php - price
  • app/Http/Requests/Admin/Master/Product/ProductRequest.php - shared_prices.*.price, variants.*.prices.*.price
  • app/Http/Requests/Admin/Master/Product/ProductVariantRequest.php - prices.*.price
  • app/Http/Requests/Admin/Finance/CashTransactionRequest.php - amount
  • app/Http/Requests/Admin/Finance/ExpenseRequest.php - amount
  • app/Http/Requests/Admin/Finance/EmployeeAdvanceRequest.php - amount
  • app/Http/Requests/Admin/Finance/PayrollAdjustmentRequest.php - amount
  • app/Http/Requests/Admin/Settings/UpdateHRRequest.php - late_penalty_amount, absent_penalty_amount

Trait Files Baru:

  • app/Concerns/CurrencyStripping.php

Catatan Penting:

  • Shared Store/Update sudah cukup pakai sometimes + if/else untuk field yang berbeda
  • Split hanya jika ruleset store dan update sangat berbeda (jarang terjadi)
  • Currency stripping selalu di prepareForValidation(), sebelum validasi jalan

14. View Conventions - Perubahan di Sesi Ini

Tech Stack

  • Framework: Inertia.js + React + TypeScript
  • UI Components: shadcn/ui (customized)
  • Styling: Tailwind CSS
  • Routing: Ziggy (type-safe routes)

Directory Structure

resources/js/
├── pages/           # Page components (Inertia)
├── components/      # Shared UI components
├── hooks/           # Custom React hooks
├── lib/             # Utility functions
└── routes/          # Type-safe route definitions

Page Patterns

Simple CRUD (Category, Customer, Supplier)

// State: createOpen, editing, deleting
// Components: PageHeader, FormDialog, DataTable, DeleteConfirmDialog
// Hook: useServerTable untuk pagination/search

Complex List (Transaction, Restock, Purchase, Cutting)

// Components: PageHeader, CardTable (bukan DataTable)
// Features: FilterPopover, expandable rows, card + sub-row
// Hook: useServerTable + useCardTableExpand

Columns Pattern

// Type definition untuk entity
export type Category = { id: number; name: string; };

// Factory function untuk columns
export function createCategoryColumns(params: CreateColumnsParams): ColumnDef<Category>[] {
    return [
        { accessorKey: 'name', header: ..., cell: ... },
        { id: 'actions', cell: ... RowActions ... }
    ];
}

Enum Options dari Controller

// Controller: kirim enum options ke view (pakai toSelect() dari HasValues trait)
'filterOptions' => [
    'statusOptions' => OrderStatus::toSelect(),
],
// View: gunakan options dari controller
{filterOptions.statusOptions.map((opt) => (
    <SelectItem key={opt.value} value={opt.value}>
        {opt.label}
    </SelectItem>
))}

Draft Pattern

// Hook: useXxxDraftSave (generated via createDraftHook)
export const useTransactionDraftSave = createDraftHook<TransactionDraftData>({
    save: saveTransactionDraft,
    clear: clearTransactionDraft,
});

// Usage
useTransactionDraftSave('create', draftData, userId);

Columns Pattern - Model Accessors

// ❌ Jangan format di TypeScript
import { formatCurrency } from '@/lib/utils';
{
    accessorKey: 'amount',
    cell: ({ row }) => <span>{formatCurrency(row.getValue('amount') as number)}</span>
}

// ✅ Gunakan formatted_ accessor dari model
{
    accessorKey: 'formatted_amount',
    cell: ({ row }) => <span>{row.getValue('formatted_amount') as string}</span>
}

// ✅ Tetap format di TS untuk computed values (bukan dari DB)
{
    accessorKey: 'payrolls_sum_total_amount',
    cell: ({ row }) => <span>{formatCurrency(row.getValue('payrolls_sum_total_amount') as number)}</span>
}

// ✅ Tetap format di TS untuk nested objects (bukan model attribute)
{
    cell: ({ row }) => <span>{formatCurrency(row.original.adjustment.amount)}</span>
}

Format Functions

import { formatDate, formatShortDate, formatDateTime } from '@/lib/format';
import { formatCurrency, formatNumber } from '@/lib/utils';

formatDate(dateString)           // "23 Agustus 2026 08:40"
formatShortDate(dateString)      // "23 Agust 2026"
formatDateTime(dateString)       // "23 Agustus 2026 08:40"
formatCurrency(1000000)          // "Rp 1.000.000"
formatNumber(1000000)            // "1.000.000"

Shared Components

Component Digunakan Untuk
FormDialog Modal form (create/edit) untuk simple CRUD
DataTable Table dengan server-side pagination
CardTable Card-based list dengan expandable rows
PageHeader Header halaman dengan title + actions
DeleteConfirmDialog Konfirmasi hapus
FilterPopover Filter toolbar
RowActions Action dropdown (edit/hapus)
FileUpload Upload file ke S3
RupiahInput Input currency formatting
NumberInput Input angka

Hooks

Hook Fungsi
useServerTable Pagination, search, filter server-side
useCardTableExpand Expand/collapse rows
useXxxDraft Auto-save draft (transaction, product, restock, purchase, cutting, raw-material)

Catatan Penting:

  • Enum options harus dikirim dari controller, bukan hardcoded di view
  • Format data gunakan #[Appends] attributes dari model
  • Type definitions ada di columns.tsx bersama column definitions
  • Draft hooks menggunakan createDraftHook factory