# 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`. ```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[] { 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) => ( {opt.label} ))} ``` ### Draft Pattern ```tsx // Hook: useXxxDraftSave (generated via createDraftHook) export const useTransactionDraftSave = createDraftHook({ 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 }) => {formatCurrency(row.getValue('amount') as number)} } // ✅ Gunakan formatted_ accessor dari model { accessorKey: 'formatted_amount', cell: ({ row }) => {row.getValue('formatted_amount') as string} } // ✅ Tetap format di TS untuk computed values (bukan dari DB) { accessorKey: 'payrolls_sum_total_amount', cell: ({ row }) => {formatCurrency(row.getValue('payrolls_sum_total_amount') as number)} } // ✅ Tetap format di TS untuk nested objects (bukan model attribute) { cell: ({ row }) => {formatCurrency(row.original.adjustment.amount)} } ``` ### 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