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
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
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
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
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:
__construct
index
show (jika ada)
create
store
edit
update
destroy
- 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