# 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}>`.

```php
// 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:
```php
// 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:
```php
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)
```tsx
// State: createOpen, editing, deleting
// Components: PageHeader, FormDialog, DataTable, DeleteConfirmDialog
// Hook: useServerTable untuk pagination/search
```

#### Complex List (Transaction, Restock, Purchase, Cutting)
```tsx
// Components: PageHeader, CardTable (bukan DataTable)
// Features: FilterPopover, expandable rows, card + sub-row
// Hook: useServerTable + useCardTableExpand
```

### Columns Pattern
```tsx
// 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
```php
// Controller: kirim enum options ke view (pakai toSelect() dari HasValues trait)
'filterOptions' => [
    'statusOptions' => OrderStatus::toSelect(),
],
```

```tsx
// View: gunakan options dari controller
{filterOptions.statusOptions.map((opt) => (
    <SelectItem key={opt.value} value={opt.value}>
        {opt.label}
    </SelectItem>
))}
```

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

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

### Columns Pattern - Model Accessors
```tsx
// ❌ 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
```tsx
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