From 660d0a648c4bb83ae7b59b733cc9c0be65de3d1a Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Tue, 28 Jul 2026 18:51:00 +0700 Subject: [PATCH] feat: create migration files for various tables including rejections, attendances, payrolls, leave requests, purchases, and stock mutations - Added `rejections` table to track rejection reasons and related entities. - Created `attendances` table for employee attendance records with geolocation data. - Introduced `payroll_periods`, `payrolls`, and `payroll_adjustments` tables for payroll management. - Established `leave_requests` table to manage employee leave applications. - Implemented `purchases` and `purchase_items` tables for tracking supplier purchases. - Created `media` table for file storage and management. - Added `activity_log` table for tracking system activities. - Established `orders` and `order_items` tables for managing customer orders. - Created `cuttings`, `cutting_materials`, and `cutting_results` tables for production management. - Introduced `system_configurations` table for application settings. - Added `push_subscriptions` table for managing user push notifications. - Created `homepage_configurations` table for homepage settings. - Established `product_prices` table for managing product pricing. - Added `owner_verification_requests` table for tracking ownership verification processes. - Created `notifications` table for user notifications. - Established `employee_advance_payments` table for managing advance payments to employees. - Created `retail_stock_histories` table for tracking stock changes. - Established `stok_opnames` and `stok_opname_items` tables for stock opname management. - Created `cutting_material_combinations` table for managing material combinations. - Added `restocks` and `restock_items` tables for managing stock replenishments. - Established `stock_mutations` table for tracking stock changes across various operations. --- app/Enums/CashTransactionType.php | 15 ++ app/Enums/Concerns/HasValues.php | 11 + app/Enums/CuttingStatus.php | 14 ++ app/Enums/EmployeeAdvanceStatus.php | 16 ++ app/Enums/EmploymentStatus.php | 16 ++ app/Enums/Gender.php | 13 ++ app/Enums/LeaveRequestStatus.php | 15 ++ app/Enums/OrderChannel.php | 16 ++ app/Enums/OrderStatus.php | 16 ++ app/Enums/PaymentType.php | 15 ++ app/Enums/PayrollAdjustmentType.php | 14 ++ app/Enums/PayrollPeriodStatus.php | 13 ++ app/Enums/PayrollStatus.php | 14 ++ app/Enums/PriceType.php | 13 ++ app/Enums/ProductStatus.php | 14 ++ app/Enums/ProductStockQuality.php | 14 ++ app/Enums/RawMaterialUnit.php | 17 ++ app/Enums/StokOpnameStatus.php | 16 ++ app/Models/AppNotification.php | 29 +++ app/Models/Attendance.php | 29 +++ app/Models/CashAccount.php | 33 +++ app/Models/CashTransaction.php | 78 +++++++ app/Models/Category.php | 21 ++ app/Models/Customer.php | 20 ++ app/Models/Cutting.php | 73 +++++++ app/Models/CuttingMaterial.php | 43 ++++ app/Models/CuttingMaterialCombination.php | 38 ++++ app/Models/CuttingResult.php | 34 +++ app/Models/Employee.php | 84 ++++++++ app/Models/EmployeeAdvance.php | 85 ++++++++ app/Models/Expense.php | 32 +++ app/Models/HomepageConfiguration.php | 13 ++ app/Models/LeaveRequest.php | 63 ++++++ app/Models/Order.php | 158 ++++++++++++++ app/Models/OrderItem.php | 61 ++++++ app/Models/OwnerVerificationRequest.php | 37 ++++ app/Models/Payroll.php | 74 +++++++ app/Models/PayrollAdjustment.php | 59 ++++++ app/Models/PayrollPeriod.php | 49 +++++ app/Models/Product.php | 55 +++++ app/Models/ProductCategory.php | 28 +++ app/Models/ProductPrice.php | 43 ++++ app/Models/ProductVariant.php | 46 ++++ app/Models/Purchase.php | 41 ++++ app/Models/PurchaseItem.php | 39 ++++ app/Models/PushSubscription.php | 19 ++ app/Models/RawMaterial.php | 29 +++ app/Models/RawMaterialPrice.php | 39 ++++ app/Models/Rejection.php | 26 +++ app/Models/Restock.php | 56 +++++ app/Models/RestockItem.php | 39 ++++ app/Models/RetailStockHistory.php | 37 ++++ app/Models/StockMutation.php | 39 ++++ app/Models/StokOpname.php | 72 +++++++ app/Models/StokOpnameItem.php | 55 +++++ app/Models/Supplier.php | 21 ++ app/Models/SystemConfiguration.php | 13 ++ app/Models/User.php | 197 +++++++++++++++--- app/Models/UserProfile.php | 49 +++++ composer.json | 5 +- composer.lock | 151 +++++++++++++- database/factories/AppNotificationFactory.php | 22 ++ database/factories/AttendanceFactory.php | 25 +++ database/factories/CashAccountFactory.php | 19 ++ database/factories/CashTransactionFactory.php | 23 ++ database/factories/CategoryFactory.php | 20 ++ database/factories/CustomerFactory.php | 18 ++ database/factories/CuttingFactory.php | 27 +++ .../CuttingMaterialCombinationFactory.php | 20 ++ database/factories/CuttingMaterialFactory.php | 23 ++ database/factories/CuttingResultFactory.php | 23 ++ database/factories/EmployeeAdvanceFactory.php | 24 +++ database/factories/EmployeeFactory.php | 21 ++ database/factories/ExpenseFactory.php | 21 ++ .../HomepageConfigurationFactory.php | 14 ++ database/factories/LeaveRequestFactory.php | 26 +++ database/factories/OrderFactory.php | 32 +++ database/factories/OrderItemFactory.php | 28 +++ .../OwnerVerificationRequestFactory.php | 21 ++ .../factories/PayrollAdjustmentFactory.php | 24 +++ database/factories/PayrollFactory.php | 30 +++ database/factories/PayrollPeriodFactory.php | 19 ++ database/factories/ProductCategoryFactory.php | 19 ++ database/factories/ProductFactory.php | 22 ++ database/factories/ProductPriceFactory.php | 19 ++ database/factories/ProductVariantFactory.php | 21 ++ database/factories/PurchaseFactory.php | 26 +++ database/factories/PurchaseItemFactory.php | 27 +++ .../factories/PushSubscriptionFactory.php | 20 ++ database/factories/RawMaterialFactory.php | 18 ++ .../factories/RawMaterialPriceFactory.php | 20 ++ database/factories/RejectionFactory.php | 20 ++ database/factories/RestockFactory.php | 21 ++ database/factories/RestockItemFactory.php | 27 +++ .../factories/RetailStockHistoryFactory.php | 29 +++ database/factories/StockMutationFactory.php | 28 +++ database/factories/StokOpnameFactory.php | 20 ++ database/factories/StokOpnameItemFactory.php | 27 +++ database/factories/SupplierFactory.php | 18 ++ .../factories/SystemConfigurationFactory.php | 14 ++ database/factories/UserFactory.php | 42 +--- database/factories/UserProfileFactory.php | 22 ++ .../0001_01_01_000000_create_users_table.php | 16 +- ...022_12_14_083707_create_settings_table.php | 25 +++ ...024_01_01_000000_create_passkeys_table.php | 34 --- ..._add_two_factor_columns_to_users_table.php | 34 --- ...6_09_112732_create_user_profiles_table.php | 39 ++++ ...26_06_09_112737_create_employees_table.php | 38 ++++ ..._06_09_183326_create_permission_tables.php | 137 ++++++++++++ ...6_06_09_185347_create_categories_table.php | 27 +++ ...26_06_10_080632_create_suppliers_table.php | 34 +++ ...26_06_10_083738_create_customers_table.php | 34 +++ ...026_06_10_100001_create_products_table.php | 30 +++ ...100002_create_product_categories_table.php | 21 ++ ...0_100003_create_product_variants_table.php | 31 +++ ...6_10_110001_create_raw_materials_table.php | 29 +++ ...10002_create_raw_material_prices_table.php | 30 +++ ...6_10_120001_create_cash_accounts_table.php | 29 +++ ..._120002_create_cash_transactions_table.php | 34 +++ ...026_06_10_150001_create_expenses_table.php | 30 +++ ..._160001_create_employee_advances_table.php | 40 ++++ ...6_06_10_170001_create_rejections_table.php | 29 +++ ..._06_11_100000_create_attendances_table.php | 34 +++ ...11_100001_create_payroll_periods_table.php | 32 +++ ...026_06_11_100002_create_payrolls_table.php | 37 ++++ ...00003_create_payroll_adjustments_table.php | 33 +++ ..._11_120000_create_leave_requests_table.php | 34 +++ ...26_06_11_120001_create_purchases_table.php | 33 +++ ..._11_120002_create_purchase_items_table.php | 32 +++ .../2026_06_11_120438_create_media_table.php | 32 +++ ...06_12_035838_create_activity_log_table.php | 24 +++ .../2026_06_12_100001_create_orders_table.php | 48 +++++ ..._06_12_100002_create_order_items_table.php | 34 +++ ...026_06_12_110001_create_cuttings_table.php | 36 ++++ ..._110002_create_cutting_materials_table.php | 32 +++ ...12_110003_create_cutting_results_table.php | 32 +++ ...450_create_system_configurations_table.php | 22 ++ ...163706_create_push_subscriptions_table.php | 31 +++ ...0_create_homepage_configurations_table.php | 23 ++ ..._20_235900_create_product_prices_table.php | 33 +++ ...eate_owner_verification_requests_table.php | 31 +++ ...6_27_130700_create_notifications_table.php | 31 +++ ...create_employee_advance_payments_table.php | 31 +++ ...00_create_retail_stock_histories_table.php | 32 +++ ...06_28_110000_create_stok_opnames_table.php | 33 +++ ..._110001_create_stok_opname_items_table.php | 33 +++ ...te_cutting_material_combinations_table.php | 28 +++ ...026_07_13_000001_create_restocks_table.php | 32 +++ ...7_13_000002_create_restock_items_table.php | 32 +++ ...27_000001_create_stock_mutations_table.php | 34 +++ 150 files changed, 4890 insertions(+), 144 deletions(-) create mode 100644 app/Enums/CashTransactionType.php create mode 100644 app/Enums/Concerns/HasValues.php create mode 100644 app/Enums/CuttingStatus.php create mode 100644 app/Enums/EmployeeAdvanceStatus.php create mode 100644 app/Enums/EmploymentStatus.php create mode 100644 app/Enums/Gender.php create mode 100644 app/Enums/LeaveRequestStatus.php create mode 100644 app/Enums/OrderChannel.php create mode 100644 app/Enums/OrderStatus.php create mode 100644 app/Enums/PaymentType.php create mode 100644 app/Enums/PayrollAdjustmentType.php create mode 100644 app/Enums/PayrollPeriodStatus.php create mode 100644 app/Enums/PayrollStatus.php create mode 100644 app/Enums/PriceType.php create mode 100644 app/Enums/ProductStatus.php create mode 100644 app/Enums/ProductStockQuality.php create mode 100644 app/Enums/RawMaterialUnit.php create mode 100644 app/Enums/StokOpnameStatus.php create mode 100644 app/Models/AppNotification.php create mode 100644 app/Models/Attendance.php create mode 100644 app/Models/CashAccount.php create mode 100644 app/Models/CashTransaction.php create mode 100644 app/Models/Category.php create mode 100644 app/Models/Customer.php create mode 100644 app/Models/Cutting.php create mode 100644 app/Models/CuttingMaterial.php create mode 100644 app/Models/CuttingMaterialCombination.php create mode 100644 app/Models/CuttingResult.php create mode 100644 app/Models/Employee.php create mode 100644 app/Models/EmployeeAdvance.php create mode 100644 app/Models/Expense.php create mode 100644 app/Models/HomepageConfiguration.php create mode 100644 app/Models/LeaveRequest.php create mode 100644 app/Models/Order.php create mode 100644 app/Models/OrderItem.php create mode 100644 app/Models/OwnerVerificationRequest.php create mode 100644 app/Models/Payroll.php create mode 100644 app/Models/PayrollAdjustment.php create mode 100644 app/Models/PayrollPeriod.php create mode 100644 app/Models/Product.php create mode 100644 app/Models/ProductCategory.php create mode 100644 app/Models/ProductPrice.php create mode 100644 app/Models/ProductVariant.php create mode 100644 app/Models/Purchase.php create mode 100644 app/Models/PurchaseItem.php create mode 100644 app/Models/PushSubscription.php create mode 100644 app/Models/RawMaterial.php create mode 100644 app/Models/RawMaterialPrice.php create mode 100644 app/Models/Rejection.php create mode 100644 app/Models/Restock.php create mode 100644 app/Models/RestockItem.php create mode 100644 app/Models/RetailStockHistory.php create mode 100644 app/Models/StockMutation.php create mode 100644 app/Models/StokOpname.php create mode 100644 app/Models/StokOpnameItem.php create mode 100644 app/Models/Supplier.php create mode 100644 app/Models/SystemConfiguration.php create mode 100644 app/Models/UserProfile.php create mode 100644 database/factories/AppNotificationFactory.php create mode 100644 database/factories/AttendanceFactory.php create mode 100644 database/factories/CashAccountFactory.php create mode 100644 database/factories/CashTransactionFactory.php create mode 100644 database/factories/CategoryFactory.php create mode 100644 database/factories/CustomerFactory.php create mode 100644 database/factories/CuttingFactory.php create mode 100644 database/factories/CuttingMaterialCombinationFactory.php create mode 100644 database/factories/CuttingMaterialFactory.php create mode 100644 database/factories/CuttingResultFactory.php create mode 100644 database/factories/EmployeeAdvanceFactory.php create mode 100644 database/factories/EmployeeFactory.php create mode 100644 database/factories/ExpenseFactory.php create mode 100644 database/factories/HomepageConfigurationFactory.php create mode 100644 database/factories/LeaveRequestFactory.php create mode 100644 database/factories/OrderFactory.php create mode 100644 database/factories/OrderItemFactory.php create mode 100644 database/factories/OwnerVerificationRequestFactory.php create mode 100644 database/factories/PayrollAdjustmentFactory.php create mode 100644 database/factories/PayrollFactory.php create mode 100644 database/factories/PayrollPeriodFactory.php create mode 100644 database/factories/ProductCategoryFactory.php create mode 100644 database/factories/ProductFactory.php create mode 100644 database/factories/ProductPriceFactory.php create mode 100644 database/factories/ProductVariantFactory.php create mode 100644 database/factories/PurchaseFactory.php create mode 100644 database/factories/PurchaseItemFactory.php create mode 100644 database/factories/PushSubscriptionFactory.php create mode 100644 database/factories/RawMaterialFactory.php create mode 100644 database/factories/RawMaterialPriceFactory.php create mode 100644 database/factories/RejectionFactory.php create mode 100644 database/factories/RestockFactory.php create mode 100644 database/factories/RestockItemFactory.php create mode 100644 database/factories/RetailStockHistoryFactory.php create mode 100644 database/factories/StockMutationFactory.php create mode 100644 database/factories/StokOpnameFactory.php create mode 100644 database/factories/StokOpnameItemFactory.php create mode 100644 database/factories/SupplierFactory.php create mode 100644 database/factories/SystemConfigurationFactory.php create mode 100644 database/factories/UserProfileFactory.php create mode 100644 database/migrations/2022_12_14_083707_create_settings_table.php delete mode 100644 database/migrations/2024_01_01_000000_create_passkeys_table.php delete mode 100644 database/migrations/2025_08_14_170933_add_two_factor_columns_to_users_table.php create mode 100644 database/migrations/2026_06_09_112732_create_user_profiles_table.php create mode 100644 database/migrations/2026_06_09_112737_create_employees_table.php create mode 100644 database/migrations/2026_06_09_183326_create_permission_tables.php create mode 100644 database/migrations/2026_06_09_185347_create_categories_table.php create mode 100644 database/migrations/2026_06_10_080632_create_suppliers_table.php create mode 100644 database/migrations/2026_06_10_083738_create_customers_table.php create mode 100644 database/migrations/2026_06_10_100001_create_products_table.php create mode 100644 database/migrations/2026_06_10_100002_create_product_categories_table.php create mode 100644 database/migrations/2026_06_10_100003_create_product_variants_table.php create mode 100644 database/migrations/2026_06_10_110001_create_raw_materials_table.php create mode 100644 database/migrations/2026_06_10_110002_create_raw_material_prices_table.php create mode 100644 database/migrations/2026_06_10_120001_create_cash_accounts_table.php create mode 100644 database/migrations/2026_06_10_120002_create_cash_transactions_table.php create mode 100644 database/migrations/2026_06_10_150001_create_expenses_table.php create mode 100644 database/migrations/2026_06_10_160001_create_employee_advances_table.php create mode 100644 database/migrations/2026_06_10_170001_create_rejections_table.php create mode 100644 database/migrations/2026_06_11_100000_create_attendances_table.php create mode 100644 database/migrations/2026_06_11_100001_create_payroll_periods_table.php create mode 100644 database/migrations/2026_06_11_100002_create_payrolls_table.php create mode 100644 database/migrations/2026_06_11_100003_create_payroll_adjustments_table.php create mode 100644 database/migrations/2026_06_11_120000_create_leave_requests_table.php create mode 100644 database/migrations/2026_06_11_120001_create_purchases_table.php create mode 100644 database/migrations/2026_06_11_120002_create_purchase_items_table.php create mode 100644 database/migrations/2026_06_11_120438_create_media_table.php create mode 100644 database/migrations/2026_06_12_035838_create_activity_log_table.php create mode 100644 database/migrations/2026_06_12_100001_create_orders_table.php create mode 100644 database/migrations/2026_06_12_100002_create_order_items_table.php create mode 100644 database/migrations/2026_06_12_110001_create_cuttings_table.php create mode 100644 database/migrations/2026_06_12_110002_create_cutting_materials_table.php create mode 100644 database/migrations/2026_06_12_110003_create_cutting_results_table.php create mode 100644 database/migrations/2026_06_12_111450_create_system_configurations_table.php create mode 100644 database/migrations/2026_06_13_163706_create_push_subscriptions_table.php create mode 100644 database/migrations/2026_06_20_130000_create_homepage_configurations_table.php create mode 100644 database/migrations/2026_06_20_235900_create_product_prices_table.php create mode 100644 database/migrations/2026_06_26_100000_create_owner_verification_requests_table.php create mode 100644 database/migrations/2026_06_27_130700_create_notifications_table.php create mode 100644 database/migrations/2026_06_28_092748_create_employee_advance_payments_table.php create mode 100644 database/migrations/2026_06_28_094400_create_retail_stock_histories_table.php create mode 100644 database/migrations/2026_06_28_110000_create_stok_opnames_table.php create mode 100644 database/migrations/2026_06_28_110001_create_stok_opname_items_table.php create mode 100644 database/migrations/2026_07_04_000001_create_cutting_material_combinations_table.php create mode 100644 database/migrations/2026_07_13_000001_create_restocks_table.php create mode 100644 database/migrations/2026_07_13_000002_create_restock_items_table.php create mode 100644 database/migrations/2026_07_27_000001_create_stock_mutations_table.php diff --git a/app/Enums/CashTransactionType.php b/app/Enums/CashTransactionType.php new file mode 100644 index 0000000..be61fd6 --- /dev/null +++ b/app/Enums/CashTransactionType.php @@ -0,0 +1,15 @@ + 'boolean', + 'read_at' => 'datetime', + ]; + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/Attendance.php b/app/Models/Attendance.php new file mode 100644 index 0000000..55c9094 --- /dev/null +++ b/app/Models/Attendance.php @@ -0,0 +1,29 @@ + 'date:Y-m-d', + 'check_in_at' => 'datetime', + 'check_out_at' => 'datetime', + 'work_duration_minutes' => 'integer', + ]; + } + + public function employee(): BelongsTo + { + return $this->belongsTo(Employee::class); + } +} diff --git a/app/Models/CashAccount.php b/app/Models/CashAccount.php new file mode 100644 index 0000000..5b28803 --- /dev/null +++ b/app/Models/CashAccount.php @@ -0,0 +1,33 @@ + 'integer', + ]; + } + + public function createdBy(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by_id'); + } + + public function cashTransactions(): HasMany + { + return $this->hasMany(CashTransaction::class); + } +} diff --git a/app/Models/CashTransaction.php b/app/Models/CashTransaction.php new file mode 100644 index 0000000..62aca5e --- /dev/null +++ b/app/Models/CashTransaction.php @@ -0,0 +1,78 @@ + CashTransactionType::class, + 'amount' => 'integer', + 'balance_after' => 'integer', + ]; + } + + #[Scope] + protected function deposit(Builder $query): void + { + $query->where('type', CashTransactionType::DEPOSIT); + } + + #[Scope] + protected function expense(Builder $query): void + { + $query->where('type', CashTransactionType::EXPENSE); + } + + #[Scope] + protected function transfer(Builder $query): void + { + $query->where('type', CashTransactionType::TRANSFER); + } + + #[Scope] + protected function withdrawal(Builder $query): void + { + $query->where('type', CashTransactionType::WITHDRAWAL); + } + + public function cashAccount(): BelongsTo + { + return $this->belongsTo(CashAccount::class); + } + + public function createdBy(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by_id'); + } + + public function expense(): HasOne + { + return $this->hasOne(Expense::class); + } + + public function order(): HasOne + { + return $this->hasOne(Order::class); + } + + public function reference(): MorphTo + { + return $this->morphTo(); + } +} diff --git a/app/Models/Category.php b/app/Models/Category.php new file mode 100644 index 0000000..2672ac0 --- /dev/null +++ b/app/Models/Category.php @@ -0,0 +1,21 @@ +belongsToMany(Product::class, 'product_categories') + ->using(ProductCategory::class); + } +} diff --git a/app/Models/Customer.php b/app/Models/Customer.php new file mode 100644 index 0000000..6f914a6 --- /dev/null +++ b/app/Models/Customer.php @@ -0,0 +1,20 @@ +hasMany(Order::class); + } +} diff --git a/app/Models/Cutting.php b/app/Models/Cutting.php new file mode 100644 index 0000000..bc35b64 --- /dev/null +++ b/app/Models/Cutting.php @@ -0,0 +1,73 @@ + CuttingStatus::class, + 'total_material_cost' => 'integer', + 'sewing_cost' => 'integer', + 'other_cost' => 'integer', + 'cost_per_unit' => 'integer', + ]; + } + + #[Scope] + protected function cancelled(Builder $query): void + { + $query->where('status', CuttingStatus::CANCELLED); + } + + #[Scope] + protected function completed(Builder $query): void + { + $query->where('status', CuttingStatus::COMPLETED); + } + + #[Scope] + protected function inProgress(Builder $query): void + { + $query->where('status', CuttingStatus::IN_PROGRESS); + } + + public function createdBy(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by_id'); + } + + public function cuttingMaterialCombinations(): HasMany + { + return $this->hasMany(CuttingMaterialCombination::class); + } + + public function cuttingMaterials(): HasMany + { + return $this->hasMany(CuttingMaterial::class); + } + + public function cuttingResults(): HasMany + { + return $this->hasMany(CuttingResult::class); + } + + public function submittedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'submitted_by_id'); + } +} diff --git a/app/Models/CuttingMaterial.php b/app/Models/CuttingMaterial.php new file mode 100644 index 0000000..b6db294 --- /dev/null +++ b/app/Models/CuttingMaterial.php @@ -0,0 +1,43 @@ + 'decimal:2', + 'material_result' => 'integer', + ]; + } + + public function combination(): BelongsTo + { + return $this->belongsTo(CuttingMaterialCombination::class, 'combination_id'); + } + + public function cutting(): BelongsTo + { + return $this->belongsTo(Cutting::class); + } + + public function rawMaterialPrice(): BelongsTo + { + return $this->belongsTo(RawMaterialPrice::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/CuttingMaterialCombination.php b/app/Models/CuttingMaterialCombination.php new file mode 100644 index 0000000..6763d51 --- /dev/null +++ b/app/Models/CuttingMaterialCombination.php @@ -0,0 +1,38 @@ + 'integer', + ]; + } + + public function cutting(): BelongsTo + { + return $this->belongsTo(Cutting::class); + } + + public function cuttingMaterials(): HasMany + { + return $this->hasMany(CuttingMaterial::class, 'combination_id'); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/CuttingResult.php b/app/Models/CuttingResult.php new file mode 100644 index 0000000..b8ce254 --- /dev/null +++ b/app/Models/CuttingResult.php @@ -0,0 +1,34 @@ + 'integer', + 'sample' => 'integer', + 'original_outside_sample' => 'integer', + ]; + } + + public function cutting(): BelongsTo + { + return $this->belongsTo(Cutting::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/Employee.php b/app/Models/Employee.php new file mode 100644 index 0000000..a7820d9 --- /dev/null +++ b/app/Models/Employee.php @@ -0,0 +1,84 @@ + EmploymentStatus::class, + 'base_salary' => 'integer', + 'join_date' => 'date:Y-m-d', + 'resign_date' => 'date:Y-m-d', + ]; + } + + #[Scope] + protected function contract(Builder $query): void + { + $query->where('employment_status', EmploymentStatus::CONTRACT); + } + + #[Scope] + protected function fullTime(Builder $query): void + { + $query->where('employment_status', EmploymentStatus::FULL_TIME); + } + + #[Scope] + protected function internship(Builder $query): void + { + $query->where('employment_status', EmploymentStatus::INTERNSHIP); + } + + #[Scope] + protected function partTime(Builder $query): void + { + $query->where('employment_status', EmploymentStatus::PART_TIME); + } + + #[Scope] + protected function resigned(Builder $query): void + { + $query->where('employment_status', EmploymentStatus::RESIGNED); + } + + public function attendances(): HasMany + { + return $this->hasMany(Attendance::class); + } + + public function employeeAdvances(): HasMany + { + return $this->hasMany(EmployeeAdvance::class); + } + + public function leaveRequests(): HasMany + { + return $this->hasMany(LeaveRequest::class); + } + + public function payrolls(): HasMany + { + return $this->hasMany(Payroll::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/EmployeeAdvance.php b/app/Models/EmployeeAdvance.php new file mode 100644 index 0000000..9ebd0ce --- /dev/null +++ b/app/Models/EmployeeAdvance.php @@ -0,0 +1,85 @@ + EmployeeAdvanceStatus::class, + 'amount' => 'integer', + 'paid_amount' => 'integer', + 'due_date' => 'date:Y-m-d', + 'verified_at' => 'datetime', + 'paid_at' => 'datetime', + ]; + } + + #[Scope] + protected function approved(Builder $query): void + { + $query->where('status', EmployeeAdvanceStatus::APPROVED); + } + + #[Scope] + protected function cancelled(Builder $query): void + { + $query->where('status', EmployeeAdvanceStatus::CANCELLED); + } + + #[Scope] + protected function paid(Builder $query): void + { + $query->where('status', EmployeeAdvanceStatus::PAID); + } + + #[Scope] + protected function pending(Builder $query): void + { + $query->where('status', EmployeeAdvanceStatus::PENDING); + } + + #[Scope] + protected function rejected(Builder $query): void + { + $query->where('status', EmployeeAdvanceStatus::REJECTED); + } + + public function cashTransaction(): BelongsTo + { + return $this->belongsTo(CashTransaction::class); + } + + public function employee(): BelongsTo + { + return $this->belongsTo(Employee::class); + } + + public function paidBy(): BelongsTo + { + return $this->belongsTo(User::class, 'paid_by_id'); + } + + public function repaymentCashTransaction(): BelongsTo + { + return $this->belongsTo(CashTransaction::class, 'repayment_cash_transaction_id'); + } + + public function verifiedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'verified_by_id'); + } +} diff --git a/app/Models/Expense.php b/app/Models/Expense.php new file mode 100644 index 0000000..69dc055 --- /dev/null +++ b/app/Models/Expense.php @@ -0,0 +1,32 @@ + 'integer', + ]; + } + + public function cashTransaction(): BelongsTo + { + return $this->belongsTo(CashTransaction::class); + } + + public function createdBy(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by_id'); + } +} diff --git a/app/Models/HomepageConfiguration.php b/app/Models/HomepageConfiguration.php new file mode 100644 index 0000000..a58d5ec --- /dev/null +++ b/app/Models/HomepageConfiguration.php @@ -0,0 +1,13 @@ + LeaveRequestStatus::class, + 'start_date' => 'date:Y-m-d', + 'end_date' => 'date:Y-m-d', + 'total_days' => 'integer', + 'verified_at' => 'datetime', + ]; + } + + #[Scope] + protected function approved(Builder $query): void + { + $query->where('status', LeaveRequestStatus::APPROVED); + } + + #[Scope] + protected function cancelled(Builder $query): void + { + $query->where('status', LeaveRequestStatus::CANCELLED); + } + + #[Scope] + protected function pending(Builder $query): void + { + $query->where('status', LeaveRequestStatus::PENDING); + } + + #[Scope] + protected function rejected(Builder $query): void + { + $query->where('status', LeaveRequestStatus::REJECTED); + } + + public function employee(): BelongsTo + { + return $this->belongsTo(Employee::class); + } + + public function verifiedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'verified_by_id'); + } +} diff --git a/app/Models/Order.php b/app/Models/Order.php new file mode 100644 index 0000000..11a0b14 --- /dev/null +++ b/app/Models/Order.php @@ -0,0 +1,158 @@ + OrderChannel::class, + 'price_type' => PriceType::class, + 'status' => OrderStatus::class, + 'payment_type' => PaymentType::class, + 'is_affiliate' => 'boolean', + 'subtotal' => 'integer', + 'discount' => 'integer', + 'nego_price' => 'integer', + 'total_amount' => 'integer', + ]; + } + + #[Scope] + protected function cancelled(Builder $query): void + { + $query->where('status', OrderStatus::CANCELLED); + } + + #[Scope] + protected function cash(Builder $query): void + { + $query->where('payment_type', PaymentType::CASH); + } + + #[Scope] + protected function completed(Builder $query): void + { + $query->where('status', OrderStatus::COMPLETED); + } + + #[Scope] + protected function credit(Builder $query): void + { + $query->where('payment_type', PaymentType::CREDIT); + } + + #[Scope] + protected function debit(Builder $query): void + { + $query->where('payment_type', PaymentType::DEBIT); + } + + #[Scope] + protected function offline(Builder $query): void + { + $query->where('channel', OrderChannel::OFFLINE); + } + + #[Scope] + protected function online(Builder $query): void + { + $query->where('channel', OrderChannel::ONLINE); + } + + #[Scope] + protected function pending(Builder $query): void + { + $query->where('status', OrderStatus::PENDING); + } + + #[Scope] + protected function processing(Builder $query): void + { + $query->where('status', OrderStatus::PROCESSING); + } + + #[Scope] + protected function refunded(Builder $query): void + { + $query->where('status', OrderStatus::REFUNDED); + } + + #[Scope] + protected function retail(Builder $query): void + { + $query->where('price_type', PriceType::RETAIL); + } + + #[Scope] + protected function shopee(Builder $query): void + { + $query->where('channel', OrderChannel::SHOPEE); + } + + #[Scope] + protected function tiktok(Builder $query): void + { + $query->where('channel', OrderChannel::TIKTOK); + } + + #[Scope] + protected function transfer(Builder $query): void + { + $query->where('payment_type', PaymentType::TRANSFER); + } + + #[Scope] + protected function whatsapp(Builder $query): void + { + $query->where('channel', OrderChannel::WHATSAPP); + } + + #[Scope] + protected function wholesale(Builder $query): void + { + $query->where('price_type', PriceType::WHOLESALE); + } + + public function cashTransaction(): BelongsTo + { + return $this->belongsTo(CashTransaction::class); + } + + public function createdBy(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by_id'); + } + + public function customer(): BelongsTo + { + return $this->belongsTo(Customer::class); + } + + public function marketing(): BelongsTo + { + return $this->belongsTo(User::class, 'marketing_id'); + } + + public function orderItems(): HasMany + { + return $this->hasMany(OrderItem::class); + } +} diff --git a/app/Models/OrderItem.php b/app/Models/OrderItem.php new file mode 100644 index 0000000..00511e9 --- /dev/null +++ b/app/Models/OrderItem.php @@ -0,0 +1,61 @@ + ProductStockQuality::class, + 'quantity' => 'integer', + 'unit_price' => 'integer', + 'subtotal' => 'integer', + ]; + } + + #[Scope] + protected function bad(Builder $query): void + { + $query->where('stock_quality', ProductStockQuality::BAD); + } + + #[Scope] + protected function damaged(Builder $query): void + { + $query->where('stock_quality', ProductStockQuality::DAMAGED); + } + + #[Scope] + protected function good(Builder $query): void + { + $query->where('stock_quality', ProductStockQuality::GOOD); + } + + public function order(): BelongsTo + { + return $this->belongsTo(Order::class); + } + + public function productVariant(): BelongsTo + { + return $this->belongsTo(ProductVariant::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/OwnerVerificationRequest.php b/app/Models/OwnerVerificationRequest.php new file mode 100644 index 0000000..30190e5 --- /dev/null +++ b/app/Models/OwnerVerificationRequest.php @@ -0,0 +1,37 @@ + 'datetime', + ]; + } + + public function subject(): MorphTo + { + return $this->morphTo(); + } + + public function submittedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'submitted_by_id'); + } + + public function verifiedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'verified_by_id'); + } +} diff --git a/app/Models/Payroll.php b/app/Models/Payroll.php new file mode 100644 index 0000000..2f22ac8 --- /dev/null +++ b/app/Models/Payroll.php @@ -0,0 +1,74 @@ + PayrollStatus::class, + 'base_salary' => 'integer', + 'bonus_amount' => 'integer', + 'deduction_amount' => 'integer', + 'total_amount' => 'integer', + 'paid_at' => 'datetime', + ]; + } + + #[Scope] + protected function cancelled(Builder $query): void + { + $query->where('status', PayrollStatus::CANCELLED); + } + + #[Scope] + protected function paid(Builder $query): void + { + $query->where('status', PayrollStatus::PAID); + } + + #[Scope] + protected function unpaid(Builder $query): void + { + $query->where('status', PayrollStatus::UNPAID); + } + + public function cashTransaction(): BelongsTo + { + return $this->belongsTo(CashTransaction::class); + } + + public function employee(): BelongsTo + { + return $this->belongsTo(Employee::class); + } + + public function paidBy(): BelongsTo + { + return $this->belongsTo(User::class, 'paid_by_id'); + } + + public function payrollAdjustments(): HasMany + { + return $this->hasMany(PayrollAdjustment::class); + } + + public function payrollPeriod(): BelongsTo + { + return $this->belongsTo(PayrollPeriod::class); + } +} diff --git a/app/Models/PayrollAdjustment.php b/app/Models/PayrollAdjustment.php new file mode 100644 index 0000000..5e0cb6a --- /dev/null +++ b/app/Models/PayrollAdjustment.php @@ -0,0 +1,59 @@ + PayrollAdjustmentType::class, + 'amount' => 'integer', + ]; + } + + #[Scope] + protected function bonus(Builder $query): void + { + $query->where('type', PayrollAdjustmentType::BONUS); + } + + #[Scope] + protected function correction(Builder $query): void + { + $query->where('type', PayrollAdjustmentType::CORRECTION); + } + + #[Scope] + protected function deduction(Builder $query): void + { + $query->where('type', PayrollAdjustmentType::DEDUCTION); + } + + public function attendance(): BelongsTo + { + return $this->belongsTo(Attendance::class); + } + + public function createdBy(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by_id'); + } + + public function payroll(): BelongsTo + { + return $this->belongsTo(Payroll::class); + } +} diff --git a/app/Models/PayrollPeriod.php b/app/Models/PayrollPeriod.php new file mode 100644 index 0000000..a979554 --- /dev/null +++ b/app/Models/PayrollPeriod.php @@ -0,0 +1,49 @@ + PayrollPeriodStatus::class, + 'closed_at' => 'datetime', + ]; + } + + #[Scope] + protected function closed(Builder $query): void + { + $query->where('status', PayrollPeriodStatus::CLOSED); + } + + #[Scope] + protected function open(Builder $query): void + { + $query->where('status', PayrollPeriodStatus::OPEN); + } + + public function closedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'closed_by_id'); + } + + public function payrolls(): HasMany + { + return $this->hasMany(Payroll::class); + } +} diff --git a/app/Models/Product.php b/app/Models/Product.php new file mode 100644 index 0000000..8e615a1 --- /dev/null +++ b/app/Models/Product.php @@ -0,0 +1,55 @@ + ProductStatus::class, + ]; + } + + #[Scope] + protected function active(Builder $query): void + { + $query->where('status', ProductStatus::ACTIVE); + } + + #[Scope] + protected function draft(Builder $query): void + { + $query->where('status', ProductStatus::DRAFT); + } + + #[Scope] + protected function inactive(Builder $query): void + { + $query->where('status', ProductStatus::INACTIVE); + } + + public function categories(): BelongsToMany + { + return $this->belongsToMany(Category::class, 'product_categories') + ->using(ProductCategory::class); + } + + public function productVariants(): HasMany + { + return $this->hasMany(ProductVariant::class); + } +} diff --git a/app/Models/ProductCategory.php b/app/Models/ProductCategory.php new file mode 100644 index 0000000..5e80182 --- /dev/null +++ b/app/Models/ProductCategory.php @@ -0,0 +1,28 @@ +belongsTo(Category::class); + } + + public function product(): BelongsTo + { + return $this->belongsTo(Product::class); + } +} diff --git a/app/Models/ProductPrice.php b/app/Models/ProductPrice.php new file mode 100644 index 0000000..8c03e48 --- /dev/null +++ b/app/Models/ProductPrice.php @@ -0,0 +1,43 @@ + PriceType::class, + 'price' => 'integer', + ]; + } + + #[Scope] + protected function retail(Builder $query): void + { + $query->where('type', PriceType::RETAIL); + } + + #[Scope] + protected function wholesale(Builder $query): void + { + $query->where('type', PriceType::WHOLESALE); + } + + public function variant(): BelongsTo + { + return $this->belongsTo(ProductVariant::class, 'variant_id'); + } +} diff --git a/app/Models/ProductVariant.php b/app/Models/ProductVariant.php new file mode 100644 index 0000000..18a12f0 --- /dev/null +++ b/app/Models/ProductVariant.php @@ -0,0 +1,46 @@ +hasMany(OrderItem::class); + } + + public function product(): BelongsTo + { + return $this->belongsTo(Product::class); + } + + public function productPrices(): HasMany + { + return $this->hasMany(ProductPrice::class, 'variant_id'); + } + + public function restockItems(): HasMany + { + return $this->hasMany(RestockItem::class); + } + + public function retailStockHistories(): HasMany + { + return $this->hasMany(RetailStockHistory::class); + } + + public function stokOpnameItems(): HasMany + { + return $this->hasMany(StokOpnameItem::class); + } +} diff --git a/app/Models/Purchase.php b/app/Models/Purchase.php new file mode 100644 index 0000000..6891837 --- /dev/null +++ b/app/Models/Purchase.php @@ -0,0 +1,41 @@ + 'integer', + 'discount' => 'integer', + 'shipping_cost' => 'integer', + 'total' => 'integer', + ]; + } + + public function createdBy(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by_id'); + } + + public function purchaseItems(): HasMany + { + return $this->hasMany(PurchaseItem::class); + } + + public function supplier(): BelongsTo + { + return $this->belongsTo(Supplier::class); + } +} diff --git a/app/Models/PurchaseItem.php b/app/Models/PurchaseItem.php new file mode 100644 index 0000000..26a7276 --- /dev/null +++ b/app/Models/PurchaseItem.php @@ -0,0 +1,39 @@ + 'decimal:4', + 'unit_price' => 'integer', + 'subtotal' => 'integer', + ]; + } + + public function purchase(): BelongsTo + { + return $this->belongsTo(Purchase::class); + } + + public function rawMaterialPrice(): BelongsTo + { + return $this->belongsTo(RawMaterialPrice::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/PushSubscription.php b/app/Models/PushSubscription.php new file mode 100644 index 0000000..54e4e88 --- /dev/null +++ b/app/Models/PushSubscription.php @@ -0,0 +1,19 @@ +belongsTo(User::class); + } +} diff --git a/app/Models/RawMaterial.php b/app/Models/RawMaterial.php new file mode 100644 index 0000000..dc64fb7 --- /dev/null +++ b/app/Models/RawMaterial.php @@ -0,0 +1,29 @@ + RawMaterialUnit::class, + 'is_active' => 'boolean', + ]; + } + + public function rawMaterialPrices(): HasMany + { + return $this->hasMany(RawMaterialPrice::class); + } +} diff --git a/app/Models/RawMaterialPrice.php b/app/Models/RawMaterialPrice.php new file mode 100644 index 0000000..cab6540 --- /dev/null +++ b/app/Models/RawMaterialPrice.php @@ -0,0 +1,39 @@ + 'integer', + 'stock' => 'decimal:4', + ]; + } + + public function cuttingMaterials(): HasMany + { + return $this->hasMany(CuttingMaterial::class); + } + + public function purchaseItems(): HasMany + { + return $this->hasMany(PurchaseItem::class); + } + + public function rawMaterial(): BelongsTo + { + return $this->belongsTo(RawMaterial::class); + } +} diff --git a/app/Models/Rejection.php b/app/Models/Rejection.php new file mode 100644 index 0000000..f958b18 --- /dev/null +++ b/app/Models/Rejection.php @@ -0,0 +1,26 @@ +morphTo(); + } + + public function rejectedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'rejected_by_id'); + } +} diff --git a/app/Models/Restock.php b/app/Models/Restock.php new file mode 100644 index 0000000..da651a7 --- /dev/null +++ b/app/Models/Restock.php @@ -0,0 +1,56 @@ + ProductStockQuality::class, + 'subtotal' => 'integer', + 'total' => 'integer', + ]; + } + + #[Scope] + protected function bad(Builder $query): void + { + $query->where('stock_type', ProductStockQuality::BAD); + } + + #[Scope] + protected function damaged(Builder $query): void + { + $query->where('stock_type', ProductStockQuality::DAMAGED); + } + + #[Scope] + protected function good(Builder $query): void + { + $query->where('stock_type', ProductStockQuality::GOOD); + } + + public function createdBy(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by_id'); + } + + public function restockItems(): HasMany + { + return $this->hasMany(RestockItem::class); + } +} diff --git a/app/Models/RestockItem.php b/app/Models/RestockItem.php new file mode 100644 index 0000000..a481e71 --- /dev/null +++ b/app/Models/RestockItem.php @@ -0,0 +1,39 @@ + 'integer', + 'unit_price' => 'integer', + 'subtotal' => 'integer', + ]; + } + + public function productVariant(): BelongsTo + { + return $this->belongsTo(ProductVariant::class); + } + + public function restock(): BelongsTo + { + return $this->belongsTo(Restock::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/RetailStockHistory.php b/app/Models/RetailStockHistory.php new file mode 100644 index 0000000..c9f040a --- /dev/null +++ b/app/Models/RetailStockHistory.php @@ -0,0 +1,37 @@ + 'integer', + 'stock_before' => 'integer', + 'retail_stock_before' => 'integer', + 'stock_after' => 'integer', + 'retail_stock_after' => 'integer', + ]; + } + + public function productVariant(): BelongsTo + { + return $this->belongsTo(ProductVariant::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/StockMutation.php b/app/Models/StockMutation.php new file mode 100644 index 0000000..2d21d42 --- /dev/null +++ b/app/Models/StockMutation.php @@ -0,0 +1,39 @@ + 'decimal:4', + 'stock_before' => 'decimal:4', + 'stock_after' => 'decimal:4', + ]; + } + + public function source(): MorphTo + { + return $this->morphTo(); + } + + public function stockable(): MorphTo + { + return $this->morphTo(); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/StokOpname.php b/app/Models/StokOpname.php new file mode 100644 index 0000000..eee252b --- /dev/null +++ b/app/Models/StokOpname.php @@ -0,0 +1,72 @@ + StokOpnameStatus::class, + 'opname_date' => 'date:Y-m-d', + ]; + } + + #[Scope] + protected function cancelled(Builder $query): void + { + $query->where('status', StokOpnameStatus::CANCELLED); + } + + #[Scope] + protected function completed(Builder $query): void + { + $query->where('status', StokOpnameStatus::COMPLETED); + } + + #[Scope] + protected function draft(Builder $query): void + { + $query->where('status', StokOpnameStatus::DRAFT); + } + + #[Scope] + protected function inProgress(Builder $query): void + { + $query->where('status', StokOpnameStatus::IN_PROGRESS); + } + + #[Scope] + protected function verified(Builder $query): void + { + $query->where('status', StokOpnameStatus::VERIFIED); + } + + public function createdBy(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by_id'); + } + + public function stokOpnameItems(): HasMany + { + return $this->hasMany(StokOpnameItem::class); + } + + public function verifiedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'verified_by_id'); + } +} diff --git a/app/Models/StokOpnameItem.php b/app/Models/StokOpnameItem.php new file mode 100644 index 0000000..a8a2afa --- /dev/null +++ b/app/Models/StokOpnameItem.php @@ -0,0 +1,55 @@ + ProductStockQuality::class, + 'system_stock' => 'integer', + 'physical_stock' => 'integer', + 'difference' => 'integer', + ]; + } + + #[Scope] + protected function bad(Builder $query): void + { + $query->where('stock_quality', ProductStockQuality::BAD); + } + + #[Scope] + protected function damaged(Builder $query): void + { + $query->where('stock_quality', ProductStockQuality::DAMAGED); + } + + #[Scope] + protected function good(Builder $query): void + { + $query->where('stock_quality', ProductStockQuality::GOOD); + } + + public function productVariant(): BelongsTo + { + return $this->belongsTo(ProductVariant::class); + } + + public function stokOpname(): BelongsTo + { + return $this->belongsTo(StokOpname::class); + } +} diff --git a/app/Models/Supplier.php b/app/Models/Supplier.php new file mode 100644 index 0000000..4d8a161 --- /dev/null +++ b/app/Models/Supplier.php @@ -0,0 +1,21 @@ +hasMany(Purchase::class); + } +} diff --git a/app/Models/SystemConfiguration.php b/app/Models/SystemConfiguration.php new file mode 100644 index 0000000..ebfc112 --- /dev/null +++ b/app/Models/SystemConfiguration.php @@ -0,0 +1,13 @@ + */ - use HasFactory, Notifiable, PasskeyAuthenticatable, TwoFactorAuthenticatable; + use HasFactory, HasRoles, Notifiable, SoftDeletes, TwoFactorAuthenticatable; - /** - * Get the attributes that should be cast. - * - * @return array - */ protected function casts(): array { return [ 'email_verified_at' => 'datetime', + 'is_active' => 'boolean', + 'last_login_at' => 'datetime', 'password' => 'hashed', 'two_factor_confirmed_at' => 'datetime', ]; } + + #[Scope] + protected function active(Builder $query): void + { + $query->where('is_active', true); + } + + public function attendances(): HasMany + { + return $this->hasMany(Attendance::class); + } + + public function cashAccounts(): HasMany + { + return $this->hasMany(CashAccount::class, 'created_by_id'); + } + + public function cashTransactions(): HasMany + { + return $this->hasMany(CashTransaction::class, 'created_by_id'); + } + + public function createdCuttings(): HasMany + { + return $this->hasMany(Cutting::class, 'created_by_id'); + } + + public function createdExpenses(): HasMany + { + return $this->hasMany(Expense::class, 'created_by_id'); + } + + public function createdOrders(): HasMany + { + return $this->hasMany(Order::class, 'created_by_id'); + } + + public function createdPayrollAdjustments(): HasMany + { + return $this->hasMany(PayrollAdjustment::class, 'created_by_id'); + } + + public function createdPurchases(): HasMany + { + return $this->hasMany(Purchase::class, 'created_by_id'); + } + + public function createdRestocks(): HasMany + { + return $this->hasMany(Restock::class, 'created_by_id'); + } + + public function employee(): HasOne + { + return $this->hasOne(Employee::class); + } + + public function employeeAdvancesPaid(): HasMany + { + return $this->hasMany(EmployeeAdvance::class, 'paid_by_id'); + } + + public function employeeAdvancesVerified(): HasMany + { + return $this->hasMany(EmployeeAdvance::class, 'verified_by_id'); + } + + public function leaveRequestsVerified(): HasMany + { + return $this->hasMany(LeaveRequest::class, 'verified_by_id'); + } + + public function marketingOrders(): HasMany + { + return $this->hasMany(Order::class, 'marketing_id'); + } + + public function notifications(): HasMany + { + return $this->hasMany(AppNotification::class); + } + + public function orderItems(): HasMany + { + return $this->hasMany(OrderItem::class); + } + + public function ownerVerificationRequestsSubmitted(): HasMany + { + return $this->hasMany(OwnerVerificationRequest::class, 'submitted_by_id'); + } + + public function ownerVerificationRequestsVerified(): HasMany + { + return $this->hasMany(OwnerVerificationRequest::class, 'verified_by_id'); + } + + public function paidPayrolls(): HasMany + { + return $this->hasMany(Payroll::class, 'paid_by_id'); + } + + public function payrollPeriodsClosed(): HasMany + { + return $this->hasMany(PayrollPeriod::class, 'closed_by_id'); + } + + public function purchaseItems(): HasMany + { + return $this->hasMany(PurchaseItem::class); + } + + public function pushSubscriptions(): HasMany + { + return $this->hasMany(PushSubscription::class); + } + + public function rejections(): HasMany + { + return $this->hasMany(Rejection::class, 'rejected_by_id'); + } + + public function restockItems(): HasMany + { + return $this->hasMany(RestockItem::class); + } + + public function retailStockHistories(): HasMany + { + return $this->hasMany(RetailStockHistory::class); + } + + public function stockMutations(): HasMany + { + return $this->hasMany(StockMutation::class); + } + + public function stokOpnamesCreated(): HasMany + { + return $this->hasMany(StokOpname::class, 'created_by_id'); + } + + public function stokOpnamesVerified(): HasMany + { + return $this->hasMany(StokOpname::class, 'verified_by_id'); + } + + public function submittedCuttings(): HasMany + { + return $this->hasMany(Cutting::class, 'submitted_by_id'); + } + + public function userProfile(): HasOne + { + return $this->hasOne(UserProfile::class); + } } diff --git a/app/Models/UserProfile.php b/app/Models/UserProfile.php new file mode 100644 index 0000000..3eb3a3f --- /dev/null +++ b/app/Models/UserProfile.php @@ -0,0 +1,49 @@ + Gender::class, + 'birth_date' => 'date:Y-m-d', + ]; + } + + #[Scope] + protected function female(Builder $query): void + { + $query->where('gender', Gender::FEMALE); + } + + #[Scope] + protected function hasPhoneNumber(Builder $query): void + { + $query->whereNotNull('phone_number'); + } + + #[Scope] + protected function male(Builder $query): void + { + $query->where('gender', Gender::MALE); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/composer.json b/composer.json index ed7a493..6ce1808 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ "laravel/fortify": "^1.37.2", "laravel/framework": "^13.17", "laravel/tinker": "^3.0", - "laravel/wayfinder": "^0.1.14" + "laravel/wayfinder": "^0.1.14", + "spatie/laravel-permission": "^8.3" }, "require-dev": { "fakerphp/faker": "^1.24", @@ -114,4 +115,4 @@ }, "minimum-stability": "stable", "prefer-stable": true -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index d89dba1..1c0822b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "69a97377cfea5f400db9607e6d000338", + "content-hash": "06614299e9ee6d35887901c6b34eb24b", "packages": [ { "name": "bacon/bacon-qr-code", @@ -4122,6 +4122,155 @@ }, "time": "2026-06-18T03:57:49+00:00" }, + { + "name": "spatie/laravel-package-tools", + "version": "1.93.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-package-tools.git", + "reference": "d5552849801f2642aea710557463234b59ef65eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/d5552849801f2642aea710557463234b59ef65eb", + "reference": "d5552849801f2642aea710557463234b59ef65eb", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^10.0|^11.0|^12.0|^13.0", + "php": "^8.1" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "orchestra/testbench": "^8.0|^9.2|^10.0|^11.0", + "pestphp/pest": "^2.1|^3.1|^4.0", + "phpunit/php-code-coverage": "^10.0|^11.0|^12.0", + "phpunit/phpunit": "^10.5|^11.5|^12.5", + "spatie/pest-plugin-test-time": "^2.2|^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\LaravelPackageTools\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Tools for creating Laravel packages", + "homepage": "https://github.com/spatie/laravel-package-tools", + "keywords": [ + "laravel-package-tools", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-package-tools/issues", + "source": "https://github.com/spatie/laravel-package-tools/tree/1.93.1" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-05-19T14:06:37+00:00" + }, + { + "name": "spatie/laravel-permission", + "version": "8.3.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "60e8ed5b2fbf043c2264433fc2680c76b8b66aa6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/60e8ed5b2fbf043c2264433fc2680c76b8b66aa6", + "reference": "60e8ed5b2fbf043c2264433fc2680c76b8b66aa6", + "shasum": "" + }, + "require": { + "illuminate/auth": "^12.0|^13.0", + "illuminate/container": "^12.0|^13.0", + "illuminate/contracts": "^12.0|^13.0", + "illuminate/database": "^12.0|^13.0", + "php": "^8.3", + "spatie/laravel-package-tools": "^1.0" + }, + "require-dev": { + "larastan/larastan": "^3.9", + "laravel/octane": "^2.0", + "laravel/passport": "^13.0", + "laravel/pint": "^1.0", + "orchestra/testbench": "^10.0|^11.0", + "pestphp/pest": "^3.0|^4.0", + "pestphp/pest-plugin-laravel": "^3.0|^4.1", + "phpstan/phpstan": "^2.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "8.x-dev", + "dev-master": "8.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Permission\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 12 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-permission/issues", + "source": "https://github.com/spatie/laravel-permission/tree/8.3.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-07-03T15:36:01+00:00" + }, { "name": "spomky-labs/cbor-php", "version": "3.3.0", diff --git a/database/factories/AppNotificationFactory.php b/database/factories/AppNotificationFactory.php new file mode 100644 index 0000000..d53ac1c --- /dev/null +++ b/database/factories/AppNotificationFactory.php @@ -0,0 +1,22 @@ + User::factory(), + 'title' => fake()->sentence(), + 'body' => fake()->paragraph(), + 'url' => fake()->url(), + 'is_read' => false, + 'read_at' => null, + ]; + } +} diff --git a/database/factories/AttendanceFactory.php b/database/factories/AttendanceFactory.php new file mode 100644 index 0000000..1df2b29 --- /dev/null +++ b/database/factories/AttendanceFactory.php @@ -0,0 +1,25 @@ + Employee::factory(), + 'attendance_date' => fake()->date(), + 'check_in_at' => fake()->dateTimeBetween('-1 day', 'now'), + 'check_out_at' => fake()->dateTimeBetween('now', '+1 day'), + 'check_in_latitude' => fake()->latitude(), + 'check_in_longitude' => fake()->longitude(), + 'check_out_latitude' => fake()->latitude(), + 'check_out_longitude' => fake()->longitude(), + 'work_duration_minutes' => fake()->numberBetween(240, 600), + ]; + } +} diff --git a/database/factories/CashAccountFactory.php b/database/factories/CashAccountFactory.php new file mode 100644 index 0000000..119b18b --- /dev/null +++ b/database/factories/CashAccountFactory.php @@ -0,0 +1,19 @@ + User::factory(), + 'name' => fake()->words(2, true), + 'balance' => fake()->numberBetween(0, 100000000), + ]; + } +} diff --git a/database/factories/CashTransactionFactory.php b/database/factories/CashTransactionFactory.php new file mode 100644 index 0000000..e29dbba --- /dev/null +++ b/database/factories/CashTransactionFactory.php @@ -0,0 +1,23 @@ + CashAccount::factory(), + 'created_by_id' => User::factory(), + 'amount' => fake()->numberBetween(1000, 1000000), + 'balance_after' => fake()->numberBetween(1000, 10000000), + 'type' => fake()->randomElement(['deposit', 'withdrawal', 'expense', 'transfer']), + 'description' => fake()->sentence(), + ]; + } +} diff --git a/database/factories/CategoryFactory.php b/database/factories/CategoryFactory.php new file mode 100644 index 0000000..7322fdf --- /dev/null +++ b/database/factories/CategoryFactory.php @@ -0,0 +1,20 @@ +unique()->word(); + + return [ + 'name' => $name, + 'slug' => Str::slug($name), + ]; + } +} diff --git a/database/factories/CustomerFactory.php b/database/factories/CustomerFactory.php new file mode 100644 index 0000000..d3e3e96 --- /dev/null +++ b/database/factories/CustomerFactory.php @@ -0,0 +1,18 @@ + fake()->name(), + 'phone_number' => fake()->phoneNumber(), + 'address' => fake()->address(), + ]; + } +} diff --git a/database/factories/CuttingFactory.php b/database/factories/CuttingFactory.php new file mode 100644 index 0000000..fc27349 --- /dev/null +++ b/database/factories/CuttingFactory.php @@ -0,0 +1,27 @@ +numberBetween(50000, 5000000); + $sewingCost = fake()->numberBetween(10000, 500000); + $otherCost = fake()->numberBetween(0, 200000); + + return [ + 'created_by_id' => User::factory(), + 'status' => fake()->randomElement(['in_progress', 'completed', 'cancelled']), + 'description' => fake()->sentence(), + 'total_material_cost' => $materialCost, + 'sewing_cost' => $sewingCost, + 'other_cost' => $otherCost, + 'cost_per_unit' => fake()->numberBetween(1000, 50000), + ]; + } +} diff --git a/database/factories/CuttingMaterialCombinationFactory.php b/database/factories/CuttingMaterialCombinationFactory.php new file mode 100644 index 0000000..7638d5e --- /dev/null +++ b/database/factories/CuttingMaterialCombinationFactory.php @@ -0,0 +1,20 @@ + User::factory(), + 'cutting_id' => Cutting::factory(), + 'material_result' => fake()->numberBetween(0, 100), + ]; + } +} diff --git a/database/factories/CuttingMaterialFactory.php b/database/factories/CuttingMaterialFactory.php new file mode 100644 index 0000000..72bf953 --- /dev/null +++ b/database/factories/CuttingMaterialFactory.php @@ -0,0 +1,23 @@ + User::factory(), + 'cutting_id' => Cutting::factory(), + 'raw_material_price_id' => RawMaterialPrice::factory(), + 'material_usage' => fake()->randomFloat(2, 0.1, 100), + 'material_result' => fake()->numberBetween(0, 100), + ]; + } +} diff --git a/database/factories/CuttingResultFactory.php b/database/factories/CuttingResultFactory.php new file mode 100644 index 0000000..3fbf868 --- /dev/null +++ b/database/factories/CuttingResultFactory.php @@ -0,0 +1,23 @@ + User::factory(), + 'cutting_id' => Cutting::factory(), + 'product_name' => fake()->words(2, true), + 'cutting_result' => fake()->numberBetween(1, 500), + 'sample' => fake()->numberBetween(0, 50), + 'original_outside_sample' => fake()->numberBetween(0, 20), + ]; + } +} diff --git a/database/factories/EmployeeAdvanceFactory.php b/database/factories/EmployeeAdvanceFactory.php new file mode 100644 index 0000000..96fac1a --- /dev/null +++ b/database/factories/EmployeeAdvanceFactory.php @@ -0,0 +1,24 @@ + Employee::factory(), + 'paid_by_id' => User::factory(), + 'amount' => fake()->numberBetween(50000, 1000000), + 'paid_amount' => 0, + 'description' => fake()->sentence(), + 'due_date' => fake()->date(), + 'status' => fake()->randomElement(['pending', 'approved', 'paid', 'rejected', 'cancelled']), + ]; + } +} diff --git a/database/factories/EmployeeFactory.php b/database/factories/EmployeeFactory.php new file mode 100644 index 0000000..603b91a --- /dev/null +++ b/database/factories/EmployeeFactory.php @@ -0,0 +1,21 @@ + User::factory(), + 'join_date' => fake()->date(), + 'resign_date' => null, + 'employment_status' => fake()->randomElement(['full_time', 'part_time', 'contract', 'internship', 'resigned']), + 'base_salary' => fake()->numberBetween(1000000, 10000000), + ]; + } +} diff --git a/database/factories/ExpenseFactory.php b/database/factories/ExpenseFactory.php new file mode 100644 index 0000000..e220357 --- /dev/null +++ b/database/factories/ExpenseFactory.php @@ -0,0 +1,21 @@ + CashTransaction::factory(), + 'created_by_id' => User::factory(), + 'amount' => fake()->numberBetween(1000, 1000000), + 'description' => fake()->sentence(), + ]; + } +} diff --git a/database/factories/HomepageConfigurationFactory.php b/database/factories/HomepageConfigurationFactory.php new file mode 100644 index 0000000..d9ee267 --- /dev/null +++ b/database/factories/HomepageConfigurationFactory.php @@ -0,0 +1,14 @@ +date(); + $end = fake()->dateTimeBetween($start, '+7 days')->format('Y-m-d'); + + return [ + 'employee_id' => Employee::factory(), + 'start_date' => $start, + 'end_date' => $end, + 'total_days' => fake()->numberBetween(1, 7), + 'status' => fake()->randomElement(['pending', 'approved', 'rejected', 'cancelled']), + 'verified_at' => null, + ]; + } +} diff --git a/database/factories/OrderFactory.php b/database/factories/OrderFactory.php new file mode 100644 index 0000000..b99aba0 --- /dev/null +++ b/database/factories/OrderFactory.php @@ -0,0 +1,32 @@ +numberBetween(10000, 1000000); + $discount = fake()->numberBetween(0, $subtotal); + + return [ + 'customer_id' => Customer::factory(), + 'created_by_id' => User::factory(), + 'order_number' => fake()->unique()->numerify('ORD-####'), + 'channel' => fake()->randomElement(['offline', 'online', 'whatsapp', 'shopee', 'tiktok']), + 'price_type' => fake()->randomElement(['retail', 'wholesale']), + 'status' => 'pending', + 'payment_type' => fake()->randomElement(['cash', 'transfer', 'debit', 'credit']), + 'is_affiliate' => false, + 'subtotal' => $subtotal, + 'discount' => $discount, + 'total_amount' => $subtotal - $discount, + 'notes' => fake()->sentence(), + ]; + } +} diff --git a/database/factories/OrderItemFactory.php b/database/factories/OrderItemFactory.php new file mode 100644 index 0000000..4a2a230 --- /dev/null +++ b/database/factories/OrderItemFactory.php @@ -0,0 +1,28 @@ +numberBetween(1, 50); + $unitPrice = fake()->numberBetween(1000, 50000); + + return [ + 'order_id' => Order::factory(), + 'user_id' => User::factory(), + 'product_variant_id' => ProductVariant::factory(), + 'stock_quality' => 'good', + 'quantity' => $quantity, + 'unit_price' => $unitPrice, + 'subtotal' => $quantity * $unitPrice, + ]; + } +} diff --git a/database/factories/OwnerVerificationRequestFactory.php b/database/factories/OwnerVerificationRequestFactory.php new file mode 100644 index 0000000..e96e25e --- /dev/null +++ b/database/factories/OwnerVerificationRequestFactory.php @@ -0,0 +1,21 @@ + fake()->randomElement(['create', 'update', 'delete']), + 'status' => 'pending', + 'submitted_by_id' => User::factory(), + 'payload' => json_encode(['key' => fake()->word(), 'value' => fake()->sentence()]), + 'verified_at' => null, + ]; + } +} diff --git a/database/factories/PayrollAdjustmentFactory.php b/database/factories/PayrollAdjustmentFactory.php new file mode 100644 index 0000000..687ad93 --- /dev/null +++ b/database/factories/PayrollAdjustmentFactory.php @@ -0,0 +1,24 @@ + Payroll::factory(), + 'attendance_id' => Attendance::factory(), + 'created_by_id' => User::factory(), + 'type' => fake()->randomElement(['bonus', 'deduction', 'correction']), + 'amount' => fake()->numberBetween(10000, 500000), + 'description' => fake()->sentence(), + ]; + } +} diff --git a/database/factories/PayrollFactory.php b/database/factories/PayrollFactory.php new file mode 100644 index 0000000..f09f377 --- /dev/null +++ b/database/factories/PayrollFactory.php @@ -0,0 +1,30 @@ +numberBetween(1000000, 10000000); + $bonus = fake()->numberBetween(0, 500000); + $deduction = fake()->numberBetween(0, 200000); + + return [ + 'payroll_period_id' => PayrollPeriod::factory(), + 'employee_id' => Employee::factory(), + 'base_salary' => $baseSalary, + 'bonus_amount' => $bonus, + 'deduction_amount' => $deduction, + 'total_amount' => $baseSalary + $bonus - $deduction, + 'status' => fake()->randomElement(['unpaid', 'paid', 'cancelled']), + 'paid_at' => null, + ]; + } +} diff --git a/database/factories/PayrollPeriodFactory.php b/database/factories/PayrollPeriodFactory.php new file mode 100644 index 0000000..e67d2e1 --- /dev/null +++ b/database/factories/PayrollPeriodFactory.php @@ -0,0 +1,19 @@ + fake()->year(), + 'month' => fake()->numberBetween(1, 12), + 'status' => fake()->randomElement(['open', 'closed']), + 'closed_at' => null, + ]; + } +} diff --git a/database/factories/ProductCategoryFactory.php b/database/factories/ProductCategoryFactory.php new file mode 100644 index 0000000..4f56d37 --- /dev/null +++ b/database/factories/ProductCategoryFactory.php @@ -0,0 +1,19 @@ + Product::factory(), + 'category_id' => Category::factory(), + ]; + } +} diff --git a/database/factories/ProductFactory.php b/database/factories/ProductFactory.php new file mode 100644 index 0000000..409ea99 --- /dev/null +++ b/database/factories/ProductFactory.php @@ -0,0 +1,22 @@ +unique()->words(2, true); + + return [ + 'name' => $name, + 'slug' => Str::slug($name), + 'description' => fake()->sentence(), + 'status' => fake()->randomElement(['active', 'inactive', 'draft']), + ]; + } +} diff --git a/database/factories/ProductPriceFactory.php b/database/factories/ProductPriceFactory.php new file mode 100644 index 0000000..82bc621 --- /dev/null +++ b/database/factories/ProductPriceFactory.php @@ -0,0 +1,19 @@ + ProductVariant::factory(), + 'type' => fake()->randomElement(['retail', 'wholesale']), + 'price' => fake()->numberBetween(1000, 1000000), + ]; + } +} diff --git a/database/factories/ProductVariantFactory.php b/database/factories/ProductVariantFactory.php new file mode 100644 index 0000000..4bb2952 --- /dev/null +++ b/database/factories/ProductVariantFactory.php @@ -0,0 +1,21 @@ + Product::factory(), + 'name' => fake()->words(2, true), + 'stock' => fake()->numberBetween(0, 1000), + 'reject_stock' => fake()->numberBetween(0, 100), + 'retail_stock' => fake()->numberBetween(0, 500), + ]; + } +} diff --git a/database/factories/PurchaseFactory.php b/database/factories/PurchaseFactory.php new file mode 100644 index 0000000..be911d7 --- /dev/null +++ b/database/factories/PurchaseFactory.php @@ -0,0 +1,26 @@ +numberBetween(50000, 5000000); + + return [ + 'supplier_id' => Supplier::factory(), + 'created_by_id' => User::factory(), + 'subtotal' => $subtotal, + 'discount' => fake()->numberBetween(0, 500000), + 'shipping_cost' => fake()->numberBetween(0, 200000), + 'total' => $subtotal + fake()->numberBetween(0, 200000) - fake()->numberBetween(0, 500000), + 'notes' => fake()->sentence(), + ]; + } +} diff --git a/database/factories/PurchaseItemFactory.php b/database/factories/PurchaseItemFactory.php new file mode 100644 index 0000000..387cc55 --- /dev/null +++ b/database/factories/PurchaseItemFactory.php @@ -0,0 +1,27 @@ +randomFloat(4, 0.5, 100); + $unitPrice = fake()->numberBetween(1000, 100000); + + return [ + 'purchase_id' => Purchase::factory(), + 'user_id' => User::factory(), + 'raw_material_price_id' => RawMaterialPrice::factory(), + 'quantity' => $quantity, + 'unit_price' => $unitPrice, + 'subtotal' => (int) ($quantity * $unitPrice), + ]; + } +} diff --git a/database/factories/PushSubscriptionFactory.php b/database/factories/PushSubscriptionFactory.php new file mode 100644 index 0000000..2ffe44c --- /dev/null +++ b/database/factories/PushSubscriptionFactory.php @@ -0,0 +1,20 @@ + User::factory(), + 'endpoint' => fake()->url(), + 'public_key' => fake()->sha256(), + 'auth_token' => fake()->sha256(), + ]; + } +} diff --git a/database/factories/RawMaterialFactory.php b/database/factories/RawMaterialFactory.php new file mode 100644 index 0000000..37f3463 --- /dev/null +++ b/database/factories/RawMaterialFactory.php @@ -0,0 +1,18 @@ + fake()->unique()->words(2, true), + 'unit' => fake()->randomElement(['kg', 'meter', 'pcs', 'roll', 'liter', 'pack']), + 'is_active' => true, + ]; + } +} diff --git a/database/factories/RawMaterialPriceFactory.php b/database/factories/RawMaterialPriceFactory.php new file mode 100644 index 0000000..3572b82 --- /dev/null +++ b/database/factories/RawMaterialPriceFactory.php @@ -0,0 +1,20 @@ + RawMaterial::factory(), + 'variant' => fake()->words(2, true), + 'price' => fake()->numberBetween(1000, 500000), + 'stock' => fake()->randomFloat(4, 0, 1000), + ]; + } +} diff --git a/database/factories/RejectionFactory.php b/database/factories/RejectionFactory.php new file mode 100644 index 0000000..47c2441 --- /dev/null +++ b/database/factories/RejectionFactory.php @@ -0,0 +1,20 @@ + fake()->word(), + 'rejectable_id' => 1, + 'rejected_by_id' => User::factory(), + 'reason' => fake()->sentence(), + ]; + } +} diff --git a/database/factories/RestockFactory.php b/database/factories/RestockFactory.php new file mode 100644 index 0000000..20bdf0d --- /dev/null +++ b/database/factories/RestockFactory.php @@ -0,0 +1,21 @@ + User::factory(), + 'subtotal' => fake()->numberBetween(50000, 5000000), + 'total' => fake()->numberBetween(50000, 5000000), + 'notes' => fake()->sentence(), + 'stock_type' => fake()->randomElement(['good', 'bad', 'damaged']), + ]; + } +} diff --git a/database/factories/RestockItemFactory.php b/database/factories/RestockItemFactory.php new file mode 100644 index 0000000..85d79ca --- /dev/null +++ b/database/factories/RestockItemFactory.php @@ -0,0 +1,27 @@ +numberBetween(1, 200); + $unitPrice = fake()->numberBetween(1000, 50000); + + return [ + 'restock_id' => Restock::factory(), + 'user_id' => User::factory(), + 'product_variant_id' => ProductVariant::factory(), + 'quantity' => $quantity, + 'unit_price' => $unitPrice, + 'subtotal' => $quantity * $unitPrice, + ]; + } +} diff --git a/database/factories/RetailStockHistoryFactory.php b/database/factories/RetailStockHistoryFactory.php new file mode 100644 index 0000000..536a1a9 --- /dev/null +++ b/database/factories/RetailStockHistoryFactory.php @@ -0,0 +1,29 @@ +numberBetween(0, 500); + $retailBefore = fake()->numberBetween(0, 200); + $quantity = fake()->numberBetween(1, 100); + + return [ + 'product_variant_id' => ProductVariant::factory(), + 'user_id' => User::factory(), + 'quantity' => $quantity, + 'stock_before' => $stockBefore, + 'retail_stock_before' => $retailBefore, + 'stock_after' => $stockBefore - $quantity, + 'retail_stock_after' => $retailBefore + $quantity, + 'notes' => fake()->sentence(), + ]; + } +} diff --git a/database/factories/StockMutationFactory.php b/database/factories/StockMutationFactory.php new file mode 100644 index 0000000..ca5b49a --- /dev/null +++ b/database/factories/StockMutationFactory.php @@ -0,0 +1,28 @@ +randomFloat(4, 0, 1000); + $quantity = fake()->randomFloat(4, -100, 100); + + return [ + 'stockable_type' => fake()->word(), + 'stockable_id' => 1, + 'type' => fake()->randomElement(['in', 'out', 'adjustment']), + 'quantity' => $quantity, + 'stock_before' => $stockBefore, + 'stock_after' => $stockBefore + $quantity, + 'stock_quality' => fake()->randomElement(['good', 'bad', 'damaged']), + 'description' => fake()->sentence(), + 'user_id' => User::factory(), + ]; + } +} diff --git a/database/factories/StokOpnameFactory.php b/database/factories/StokOpnameFactory.php new file mode 100644 index 0000000..3c2449e --- /dev/null +++ b/database/factories/StokOpnameFactory.php @@ -0,0 +1,20 @@ + User::factory(), + 'opname_date' => fake()->date(), + 'status' => fake()->randomElement(['draft', 'in_progress', 'completed', 'verified', 'cancelled']), + 'notes' => fake()->sentence(), + ]; + } +} diff --git a/database/factories/StokOpnameItemFactory.php b/database/factories/StokOpnameItemFactory.php new file mode 100644 index 0000000..94e8e07 --- /dev/null +++ b/database/factories/StokOpnameItemFactory.php @@ -0,0 +1,27 @@ +numberBetween(0, 500); + $physical = fake()->numberBetween(0, 500); + + return [ + 'stok_opname_id' => StokOpname::factory(), + 'product_variant_id' => ProductVariant::factory(), + 'stock_quality' => 'good', + 'system_stock' => $system, + 'physical_stock' => $physical, + 'difference' => $physical - $system, + 'notes' => fake()->sentence(), + ]; + } +} diff --git a/database/factories/SupplierFactory.php b/database/factories/SupplierFactory.php new file mode 100644 index 0000000..f72623b --- /dev/null +++ b/database/factories/SupplierFactory.php @@ -0,0 +1,18 @@ + fake()->company(), + 'phone_number' => fake()->phoneNumber(), + 'address' => fake()->address(), + ]; + } +} diff --git a/database/factories/SystemConfigurationFactory.php b/database/factories/SystemConfigurationFactory.php new file mode 100644 index 0000000..372fe96 --- /dev/null +++ b/database/factories/SystemConfigurationFactory.php @@ -0,0 +1,14 @@ + - */ class UserFactory extends Factory { - /** - * The current password being used by the factory. - */ protected static ?string $password; - /** - * Define the model's default state. - * - * @return array - */ public function definition(): array { return [ - 'name' => fake()->name(), 'email' => fake()->unique()->safeEmail(), - 'email_verified_at' => now(), + 'username' => fake()->unique()->userName(), 'password' => static::$password ??= Hash::make('password'), - 'remember_token' => Str::random(10), - 'two_factor_secret' => null, - 'two_factor_recovery_codes' => null, - 'two_factor_confirmed_at' => null, + 'is_active' => true, + 'last_login_at' => null, ]; } - - /** - * Indicate that the model's email address should be unverified. - */ - public function unverified(): static - { - return $this->state(fn (array $attributes) => [ - 'email_verified_at' => null, - ]); - } - - /** - * Indicate that the model has two-factor authentication configured. - */ - public function withTwoFactor(): static - { - return $this->state(fn (array $attributes) => [ - 'two_factor_secret' => encrypt('secret'), - 'two_factor_recovery_codes' => encrypt(json_encode(['recovery-code-1'])), - 'two_factor_confirmed_at' => now(), - ]); - } } diff --git a/database/factories/UserProfileFactory.php b/database/factories/UserProfileFactory.php new file mode 100644 index 0000000..f7aaad5 --- /dev/null +++ b/database/factories/UserProfileFactory.php @@ -0,0 +1,22 @@ + User::factory(), + 'full_name' => fake()->name(), + 'phone_number' => fake()->phoneNumber(), + 'gender' => fake()->randomElement(['male', 'female']), + 'birth_date' => fake()->date(), + 'address' => fake()->address(), + ]; + } +} diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php index 05fb5d9..37f3854 100644 --- a/database/migrations/0001_01_01_000000_create_users_table.php +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -13,12 +13,16 @@ public function up(): void { Schema::create('users', function (Blueprint $table) { $table->id(); - $table->string('name'); - $table->string('email')->unique(); - $table->timestamp('email_verified_at')->nullable(); - $table->string('password'); - $table->rememberToken(); - $table->timestamps(); + + $table->string('email', 100)->unique(); + $table->string('username', 20)->unique(); + $table->string('password', 255); + $table->boolean('is_active')->default(true); + $table->timestamp('last_login_at')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); }); Schema::create('password_reset_tokens', function (Blueprint $table) { diff --git a/database/migrations/2022_12_14_083707_create_settings_table.php b/database/migrations/2022_12_14_083707_create_settings_table.php new file mode 100644 index 0000000..33bb8a5 --- /dev/null +++ b/database/migrations/2022_12_14_083707_create_settings_table.php @@ -0,0 +1,25 @@ +id(); + + $table->string('group'); + $table->string('name'); + $table->boolean('locked')->default(false); + $table->json('payload'); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + + $table->unique(['group', 'name']); + }); + } +}; diff --git a/database/migrations/2024_01_01_000000_create_passkeys_table.php b/database/migrations/2024_01_01_000000_create_passkeys_table.php deleted file mode 100644 index cb884c3..0000000 --- a/database/migrations/2024_01_01_000000_create_passkeys_table.php +++ /dev/null @@ -1,34 +0,0 @@ -id(); - $table->foreignId('user_id')->constrained()->cascadeOnDelete(); - $table->string('name'); - $table->string('credential_id')->unique(); - $table->json('credential'); - $table->timestamp('last_used_at')->nullable(); - $table->timestamps(); - - $table->index('user_id'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('passkeys'); - } -}; diff --git a/database/migrations/2025_08_14_170933_add_two_factor_columns_to_users_table.php b/database/migrations/2025_08_14_170933_add_two_factor_columns_to_users_table.php deleted file mode 100644 index 187d974..0000000 --- a/database/migrations/2025_08_14_170933_add_two_factor_columns_to_users_table.php +++ /dev/null @@ -1,34 +0,0 @@ -text('two_factor_secret')->after('password')->nullable(); - $table->text('two_factor_recovery_codes')->after('two_factor_secret')->nullable(); - $table->timestamp('two_factor_confirmed_at')->after('two_factor_recovery_codes')->nullable(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::table('users', function (Blueprint $table) { - $table->dropColumn([ - 'two_factor_secret', - 'two_factor_recovery_codes', - 'two_factor_confirmed_at', - ]); - }); - } -}; diff --git a/database/migrations/2026_06_09_112732_create_user_profiles_table.php b/database/migrations/2026_06_09_112732_create_user_profiles_table.php new file mode 100644 index 0000000..56b2cae --- /dev/null +++ b/database/migrations/2026_06_09_112732_create_user_profiles_table.php @@ -0,0 +1,39 @@ +id(); + + $table->foreignId('user_id')->constrained('users')->cascadeOnDelete()->unique(); + + $table->string('full_name', 200); + $table->string('phone_number', 20)->nullable(); + $table->enum('gender', Gender::values())->nullable(); + $table->date('birth_date')->nullable(); + $table->text('address')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_profiles'); + } +}; diff --git a/database/migrations/2026_06_09_112737_create_employees_table.php b/database/migrations/2026_06_09_112737_create_employees_table.php new file mode 100644 index 0000000..4fbb796 --- /dev/null +++ b/database/migrations/2026_06_09_112737_create_employees_table.php @@ -0,0 +1,38 @@ +id(); + + $table->foreignId('user_id')->constrained()->cascadeOnDelete()->unique(); + + $table->date('join_date'); + $table->date('resign_date')->nullable(); + $table->enum('employment_status', EmploymentStatus::values())->default(EmploymentStatus::FULL_TIME->value); + $table->unsignedInteger('base_salary'); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('employees'); + } +}; diff --git a/database/migrations/2026_06_09_183326_create_permission_tables.php b/database/migrations/2026_06_09_183326_create_permission_tables.php new file mode 100644 index 0000000..8986275 --- /dev/null +++ b/database/migrations/2026_06_09_183326_create_permission_tables.php @@ -0,0 +1,137 @@ +id(); // permission id + $table->string('name'); + $table->string('guard_name'); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + /** + * See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered. + */ + Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { + $table->id(); // role id + if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + $table->string('name'); + $table->string('guard_name'); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + + Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { + $table->unsignedBigInteger($pivotPermission); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->cascadeOnDelete(); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } else { + $table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } + }); + + Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { + $table->unsignedBigInteger($pivotRole); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->cascadeOnDelete(); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } else { + $table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } + }); + + Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { + $table->unsignedBigInteger($pivotPermission); + $table->unsignedBigInteger($pivotRole); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->cascadeOnDelete(); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->cascadeOnDelete(); + + $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $tableNames = config('permission.table_names'); + + throw_if(empty($tableNames), 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); + + Schema::dropIfExists($tableNames['role_has_permissions']); + Schema::dropIfExists($tableNames['model_has_roles']); + Schema::dropIfExists($tableNames['model_has_permissions']); + Schema::dropIfExists($tableNames['roles']); + Schema::dropIfExists($tableNames['permissions']); + } +}; diff --git a/database/migrations/2026_06_09_185347_create_categories_table.php b/database/migrations/2026_06_09_185347_create_categories_table.php new file mode 100644 index 0000000..96d2092 --- /dev/null +++ b/database/migrations/2026_06_09_185347_create_categories_table.php @@ -0,0 +1,27 @@ +id(); + + $table->string('name', 50); + $table->string('slug', 50)->unique(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('categories'); + } +}; diff --git a/database/migrations/2026_06_10_080632_create_suppliers_table.php b/database/migrations/2026_06_10_080632_create_suppliers_table.php new file mode 100644 index 0000000..fe186e3 --- /dev/null +++ b/database/migrations/2026_06_10_080632_create_suppliers_table.php @@ -0,0 +1,34 @@ +id(); + + $table->string('name', 200); + $table->string('phone_number', 20)->nullable(); + $table->text('address')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('suppliers'); + } +}; diff --git a/database/migrations/2026_06_10_083738_create_customers_table.php b/database/migrations/2026_06_10_083738_create_customers_table.php new file mode 100644 index 0000000..df677ab --- /dev/null +++ b/database/migrations/2026_06_10_083738_create_customers_table.php @@ -0,0 +1,34 @@ +id(); + + $table->string('name', 200); + $table->string('phone_number', 20)->nullable(); + $table->text('address')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('customers'); + } +}; diff --git a/database/migrations/2026_06_10_100001_create_products_table.php b/database/migrations/2026_06_10_100001_create_products_table.php new file mode 100644 index 0000000..ad62f65 --- /dev/null +++ b/database/migrations/2026_06_10_100001_create_products_table.php @@ -0,0 +1,30 @@ +id(); + + $table->string('name', 200); + $table->string('slug', 200)->unique(); + $table->text('description')->nullable(); + $table->string('status', 20)->default(ProductStatus::ACTIVE->value); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('products'); + } +}; diff --git a/database/migrations/2026_06_10_100002_create_product_categories_table.php b/database/migrations/2026_06_10_100002_create_product_categories_table.php new file mode 100644 index 0000000..cedab59 --- /dev/null +++ b/database/migrations/2026_06_10_100002_create_product_categories_table.php @@ -0,0 +1,21 @@ +foreignId('product_id')->constrained()->cascadeOnDelete(); + $table->foreignId('category_id')->constrained()->cascadeOnDelete(); + }); + } + + public function down(): void + { + Schema::dropIfExists('product_categories'); + } +}; diff --git a/database/migrations/2026_06_10_100003_create_product_variants_table.php b/database/migrations/2026_06_10_100003_create_product_variants_table.php new file mode 100644 index 0000000..799485d --- /dev/null +++ b/database/migrations/2026_06_10_100003_create_product_variants_table.php @@ -0,0 +1,31 @@ +id(); + + $table->foreignId('product_id')->constrained('products')->cascadeOnDelete(); + + $table->string('name', 200); + $table->unsignedInteger('stock')->default(0); + $table->unsignedInteger('reject_stock')->default(0); + $table->unsignedInteger('retail_stock')->default(0); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('product_variants'); + } +}; diff --git a/database/migrations/2026_06_10_110001_create_raw_materials_table.php b/database/migrations/2026_06_10_110001_create_raw_materials_table.php new file mode 100644 index 0000000..560cbf1 --- /dev/null +++ b/database/migrations/2026_06_10_110001_create_raw_materials_table.php @@ -0,0 +1,29 @@ +id(); + + $table->string('name', 200); + $table->enum('unit', RawMaterialUnit::values()); + $table->boolean('is_active')->default(true); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('raw_materials'); + } +}; diff --git a/database/migrations/2026_06_10_110002_create_raw_material_prices_table.php b/database/migrations/2026_06_10_110002_create_raw_material_prices_table.php new file mode 100644 index 0000000..af6e358 --- /dev/null +++ b/database/migrations/2026_06_10_110002_create_raw_material_prices_table.php @@ -0,0 +1,30 @@ +id(); + + $table->foreignId('raw_material_id')->constrained()->cascadeOnDelete(); + + $table->string('variant', 200); + $table->unsignedInteger('price'); + $table->decimal('stock', 18, 4)->default(0); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('raw_material_prices'); + } +}; diff --git a/database/migrations/2026_06_10_120001_create_cash_accounts_table.php b/database/migrations/2026_06_10_120001_create_cash_accounts_table.php new file mode 100644 index 0000000..6fb03e4 --- /dev/null +++ b/database/migrations/2026_06_10_120001_create_cash_accounts_table.php @@ -0,0 +1,29 @@ +id(); + + $table->foreignId('created_by_id')->constrained('users')->restrictOnDelete(); + + $table->string('name', 200); + $table->unsignedBigInteger('balance')->default(0); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('cash_accounts'); + } +}; diff --git a/database/migrations/2026_06_10_120002_create_cash_transactions_table.php b/database/migrations/2026_06_10_120002_create_cash_transactions_table.php new file mode 100644 index 0000000..ad32588 --- /dev/null +++ b/database/migrations/2026_06_10_120002_create_cash_transactions_table.php @@ -0,0 +1,34 @@ +id(); + + $table->foreignId('cash_account_id')->constrained()->cascadeOnDelete(); + $table->foreignId('created_by_id')->constrained('users')->restrictOnDelete(); + $table->nullableMorphs('reference'); + + $table->unsignedBigInteger('amount'); + $table->unsignedBigInteger('balance_after'); + $table->enum('type', CashTransactionType::values())->default(CashTransactionType::DEPOSIT->value); + $table->string('description', 100); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('cash_transactions'); + } +}; diff --git a/database/migrations/2026_06_10_150001_create_expenses_table.php b/database/migrations/2026_06_10_150001_create_expenses_table.php new file mode 100644 index 0000000..4dc5715 --- /dev/null +++ b/database/migrations/2026_06_10_150001_create_expenses_table.php @@ -0,0 +1,30 @@ +id(); + + $table->foreignId('cash_transaction_id')->nullable()->unique()->constrained()->restrictOnDelete(); + $table->foreignId('created_by_id')->constrained('users')->restrictOnDelete(); + + $table->unsignedBigInteger('amount'); + $table->string('description', 100); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('expenses'); + } +}; diff --git a/database/migrations/2026_06_10_160001_create_employee_advances_table.php b/database/migrations/2026_06_10_160001_create_employee_advances_table.php new file mode 100644 index 0000000..773627c --- /dev/null +++ b/database/migrations/2026_06_10_160001_create_employee_advances_table.php @@ -0,0 +1,40 @@ +id(); + + $table->foreignId('paid_by_id')->nullable()->constrained('users')->restrictOnDelete(); + $table->foreignId('employee_id')->constrained()->cascadeOnDelete(); + $table->foreignId('verified_by_id')->nullable()->constrained('users')->restrictOnDelete(); + $table->foreignId('repayment_cash_transaction_id')->nullable()->unique()->constrained('cash_transactions')->restrictOnDelete(); + $table->foreignId('cash_transaction_id')->nullable()->unique()->constrained()->restrictOnDelete(); + + $table->unsignedBigInteger('amount'); + $table->unsignedBigInteger('paid_amount')->default(0); + $table->string('description', 100); + $table->date('due_date'); + $table->enum('status', EmployeeAdvanceStatus::values()) + ->default(EmployeeAdvanceStatus::PENDING->value); + $table->timestamp('verified_at')->nullable(); + $table->timestamp('paid_at')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('employee_advances'); + } +}; diff --git a/database/migrations/2026_06_10_170001_create_rejections_table.php b/database/migrations/2026_06_10_170001_create_rejections_table.php new file mode 100644 index 0000000..55168ab --- /dev/null +++ b/database/migrations/2026_06_10_170001_create_rejections_table.php @@ -0,0 +1,29 @@ +id(); + + $table->nullableMorphs('rejectable'); + $table->foreignId('rejected_by_id')->constrained('users')->restrictOnDelete(); + + $table->string('reason', 500); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('rejections'); + } +}; diff --git a/database/migrations/2026_06_11_100000_create_attendances_table.php b/database/migrations/2026_06_11_100000_create_attendances_table.php new file mode 100644 index 0000000..4f5b688 --- /dev/null +++ b/database/migrations/2026_06_11_100000_create_attendances_table.php @@ -0,0 +1,34 @@ +id(); + + $table->foreignId('employee_id')->constrained()->cascadeOnDelete(); + + $table->date('attendance_date'); + $table->dateTime('check_in_at'); + $table->dateTime('check_out_at')->nullable(); + $table->decimal('check_in_latitude', 10, 7)->nullable(); + $table->decimal('check_in_longitude', 10, 7)->nullable(); + $table->decimal('check_out_latitude', 10, 7)->nullable(); + $table->decimal('check_out_longitude', 10, 7)->nullable(); + $table->unsignedInteger('work_duration_minutes')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + }); + } + + public function down(): void + { + Schema::dropIfExists('attendances'); + } +}; diff --git a/database/migrations/2026_06_11_100001_create_payroll_periods_table.php b/database/migrations/2026_06_11_100001_create_payroll_periods_table.php new file mode 100644 index 0000000..e202062 --- /dev/null +++ b/database/migrations/2026_06_11_100001_create_payroll_periods_table.php @@ -0,0 +1,32 @@ +id(); + + $table->foreignId('closed_by_id')->nullable()->constrained('users')->nullOnDelete(); + + $table->unsignedSmallInteger('year'); + $table->unsignedTinyInteger('month'); + $table->enum('status', PayrollPeriodStatus::values())->default(PayrollPeriodStatus::OPEN->value); + $table->timestamp('closed_at')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('payroll_periods'); + } +}; diff --git a/database/migrations/2026_06_11_100002_create_payrolls_table.php b/database/migrations/2026_06_11_100002_create_payrolls_table.php new file mode 100644 index 0000000..aaf2e21 --- /dev/null +++ b/database/migrations/2026_06_11_100002_create_payrolls_table.php @@ -0,0 +1,37 @@ +id(); + + $table->foreignId('payroll_period_id')->constrained()->cascadeOnDelete(); + $table->foreignId('employee_id')->constrained()->cascadeOnDelete(); + $table->foreignId('cash_transaction_id')->nullable()->unique()->constrained()->restrictOnDelete(); + $table->foreignId('paid_by_id')->nullable()->constrained('users')->nullOnDelete(); + + $table->unsignedInteger('base_salary'); + $table->unsignedBigInteger('bonus_amount')->default(0); + $table->unsignedBigInteger('deduction_amount')->default(0); + $table->unsignedBigInteger('total_amount'); + $table->enum('status', PayrollStatus::values())->default(PayrollStatus::UNPAID->value); + $table->timestamp('paid_at')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('payrolls'); + } +}; diff --git a/database/migrations/2026_06_11_100003_create_payroll_adjustments_table.php b/database/migrations/2026_06_11_100003_create_payroll_adjustments_table.php new file mode 100644 index 0000000..749fbac --- /dev/null +++ b/database/migrations/2026_06_11_100003_create_payroll_adjustments_table.php @@ -0,0 +1,33 @@ +id(); + + $table->foreignId('payroll_id')->constrained()->cascadeOnDelete(); + $table->foreignId('attendance_id')->nullable()->constrained()->nullOnDelete(); + $table->foreignId('created_by_id')->constrained('users')->restrictOnDelete(); + + $table->enum('type', PayrollAdjustmentType::values()); + $table->unsignedBigInteger('amount'); + $table->string('description', 100); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('payroll_adjustments'); + } +}; diff --git a/database/migrations/2026_06_11_120000_create_leave_requests_table.php b/database/migrations/2026_06_11_120000_create_leave_requests_table.php new file mode 100644 index 0000000..8dae4b4 --- /dev/null +++ b/database/migrations/2026_06_11_120000_create_leave_requests_table.php @@ -0,0 +1,34 @@ +id(); + + $table->foreignId('employee_id')->constrained()->cascadeOnDelete(); + $table->foreignId('verified_by_id')->nullable()->constrained('users')->restrictOnDelete(); + + $table->date('start_date'); + $table->date('end_date'); + $table->unsignedInteger('total_days'); + $table->enum('status', LeaveRequestStatus::values())->default(LeaveRequestStatus::PENDING->value); + $table->timestamp('verified_at')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('leave_requests'); + } +}; diff --git a/database/migrations/2026_06_11_120001_create_purchases_table.php b/database/migrations/2026_06_11_120001_create_purchases_table.php new file mode 100644 index 0000000..8381b3e --- /dev/null +++ b/database/migrations/2026_06_11_120001_create_purchases_table.php @@ -0,0 +1,33 @@ +id(); + + $table->foreignId('supplier_id')->constrained()->cascadeOnDelete(); + $table->foreignId('created_by_id')->constrained('users')->cascadeOnDelete(); + + $table->unsignedBigInteger('subtotal'); + $table->unsignedBigInteger('discount')->default(0); + $table->unsignedBigInteger('shipping_cost')->default(0); + $table->unsignedBigInteger('total'); + $table->string('notes', 100)->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('purchases'); + } +}; diff --git a/database/migrations/2026_06_11_120002_create_purchase_items_table.php b/database/migrations/2026_06_11_120002_create_purchase_items_table.php new file mode 100644 index 0000000..6282625 --- /dev/null +++ b/database/migrations/2026_06_11_120002_create_purchase_items_table.php @@ -0,0 +1,32 @@ +id(); + + $table->foreignId('purchase_id')->nullable()->constrained()->cascadeOnDelete(); + $table->foreignId('user_id')->nullable()->constrained()->cascadeOnDelete(); + $table->foreignId('raw_material_price_id')->constrained()->cascadeOnDelete(); + + $table->decimal('quantity', 18, 4); + $table->unsignedBigInteger('unit_price'); + $table->unsignedBigInteger('subtotal'); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('purchase_items'); + } +}; diff --git a/database/migrations/2026_06_11_120438_create_media_table.php b/database/migrations/2026_06_11_120438_create_media_table.php new file mode 100644 index 0000000..47a4be9 --- /dev/null +++ b/database/migrations/2026_06_11_120438_create_media_table.php @@ -0,0 +1,32 @@ +id(); + + $table->morphs('model'); + $table->uuid()->nullable()->unique(); + $table->string('collection_name'); + $table->string('name'); + $table->string('file_name'); + $table->string('mime_type')->nullable(); + $table->string('disk'); + $table->string('conversions_disk')->nullable(); + $table->unsignedBigInteger('size'); + $table->json('manipulations'); + $table->json('custom_properties'); + $table->json('generated_conversions'); + $table->json('responsive_images'); + $table->unsignedInteger('order_column')->nullable()->index(); + + $table->nullableTimestamps(); + }); + } +}; diff --git a/database/migrations/2026_06_12_035838_create_activity_log_table.php b/database/migrations/2026_06_12_035838_create_activity_log_table.php new file mode 100644 index 0000000..75e0c86 --- /dev/null +++ b/database/migrations/2026_06_12_035838_create_activity_log_table.php @@ -0,0 +1,24 @@ +id(); + $table->string('log_name')->nullable()->index(); + $table->text('description'); + $table->nullableMorphs('subject', 'subject'); + $table->string('event')->nullable(); + $table->nullableMorphs('causer', 'causer'); + $table->json('attribute_changes')->nullable(); + $table->json('properties')->nullable(); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + }); + } +}; diff --git a/database/migrations/2026_06_12_100001_create_orders_table.php b/database/migrations/2026_06_12_100001_create_orders_table.php new file mode 100644 index 0000000..293f0c4 --- /dev/null +++ b/database/migrations/2026_06_12_100001_create_orders_table.php @@ -0,0 +1,48 @@ +id(); + + $table->foreignId('customer_id')->nullable()->constrained()->nullOnDelete(); + $table->foreignId('marketing_id')->nullable()->constrained('users')->nullOnDelete(); + $table->foreignId('cash_transaction_id')->nullable()->unique()->constrained()->restrictOnDelete(); + $table->foreignId('created_by_id')->constrained('users')->restrictOnDelete(); + + $table->string('order_number', 30)->unique(); + $table->enum('channel', OrderChannel::values()); + $table->enum('price_type', PriceType::values()); + $table->enum('status', OrderStatus::values())->default(OrderStatus::PENDING->value); + $table->enum('payment_type', PaymentType::values())->default(PaymentType::CASH->value); + $table->boolean('is_affiliate')->default(false); + $table->string('tiktok_order_id', 100)->nullable(); + $table->string('shopee_order_id', 100)->nullable(); + $table->unsignedBigInteger('subtotal'); + $table->unsignedBigInteger('discount')->default(0); + $table->unsignedBigInteger('nego_price')->nullable(); + $table->json('marketplace_settings_snapshot')->nullable(); + $table->unsignedBigInteger('total_amount'); + $table->text('notes')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('orders'); + } +}; diff --git a/database/migrations/2026_06_12_100002_create_order_items_table.php b/database/migrations/2026_06_12_100002_create_order_items_table.php new file mode 100644 index 0000000..7c4ba0e --- /dev/null +++ b/database/migrations/2026_06_12_100002_create_order_items_table.php @@ -0,0 +1,34 @@ +id(); + + $table->foreignId('order_id')->nullable()->constrained()->cascadeOnDelete(); + $table->foreignId('user_id')->nullable()->constrained()->cascadeOnDelete(); + $table->foreignId('product_variant_id')->constrained()->restrictOnDelete(); + + $table->enum('stock_quality', ProductStockQuality::values())->default(ProductStockQuality::GOOD->value); + $table->unsignedInteger('quantity'); + $table->unsignedBigInteger('unit_price'); + $table->unsignedBigInteger('subtotal'); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('order_items'); + } +}; diff --git a/database/migrations/2026_06_12_110001_create_cuttings_table.php b/database/migrations/2026_06_12_110001_create_cuttings_table.php new file mode 100644 index 0000000..3397b56 --- /dev/null +++ b/database/migrations/2026_06_12_110001_create_cuttings_table.php @@ -0,0 +1,36 @@ +id(); + + $table->foreignId('created_by_id')->constrained('users')->restrictOnDelete(); + $table->foreignId('submitted_by_id')->nullable()->constrained('users')->nullOnDelete(); + + $table->enum('status', CuttingStatus::values())->default(CuttingStatus::IN_PROGRESS->value); + $table->string('description', 100)->nullable(); + + $table->unsignedBigInteger('total_material_cost')->nullable(); + $table->unsignedBigInteger('sewing_cost')->default(0); + $table->unsignedBigInteger('other_cost')->default(0); + $table->unsignedBigInteger('cost_per_unit')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('cuttings'); + } +}; diff --git a/database/migrations/2026_06_12_110002_create_cutting_materials_table.php b/database/migrations/2026_06_12_110002_create_cutting_materials_table.php new file mode 100644 index 0000000..5143e6b --- /dev/null +++ b/database/migrations/2026_06_12_110002_create_cutting_materials_table.php @@ -0,0 +1,32 @@ +id(); + + $table->foreignId('user_id')->nullable()->constrained()->cascadeOnDelete(); + $table->foreignId('cutting_id')->nullable()->constrained()->cascadeOnDelete(); + $table->foreignId('raw_material_price_id')->constrained()->restrictOnDelete(); + $table->foreignId('combination_id')->nullable()->constrained('cutting_material_combinations')->cascadeOnDelete(); + + $table->decimal('material_usage', 18, 2); + $table->integer('material_result')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('cutting_materials'); + } +}; diff --git a/database/migrations/2026_06_12_110003_create_cutting_results_table.php b/database/migrations/2026_06_12_110003_create_cutting_results_table.php new file mode 100644 index 0000000..58a9d5b --- /dev/null +++ b/database/migrations/2026_06_12_110003_create_cutting_results_table.php @@ -0,0 +1,32 @@ +id(); + + $table->foreignId('user_id')->nullable()->constrained()->cascadeOnDelete(); + $table->foreignId('cutting_id')->nullable()->constrained()->cascadeOnDelete(); + + $table->string('product_name', 255)->nullable(); + $table->unsignedInteger('cutting_result')->nullable(); + $table->unsignedInteger('sample')->nullable(); + $table->unsignedInteger('original_outside_sample')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('cutting_results'); + } +}; diff --git a/database/migrations/2026_06_12_111450_create_system_configurations_table.php b/database/migrations/2026_06_12_111450_create_system_configurations_table.php new file mode 100644 index 0000000..adf29d5 --- /dev/null +++ b/database/migrations/2026_06_12_111450_create_system_configurations_table.php @@ -0,0 +1,22 @@ +id(); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + }); + } + + public function down(): void + { + Schema::dropIfExists('system_configurations'); + } +}; diff --git a/database/migrations/2026_06_13_163706_create_push_subscriptions_table.php b/database/migrations/2026_06_13_163706_create_push_subscriptions_table.php new file mode 100644 index 0000000..b6474f9 --- /dev/null +++ b/database/migrations/2026_06_13_163706_create_push_subscriptions_table.php @@ -0,0 +1,31 @@ +id(); + + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + + $table->string('endpoint', 500); + $table->string('public_key', 255)->nullable(); + $table->string('auth_token', 255)->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + + $table->unique('endpoint'); + }); + } + + public function down(): void + { + Schema::dropIfExists('push_subscriptions'); + } +}; diff --git a/database/migrations/2026_06_20_130000_create_homepage_configurations_table.php b/database/migrations/2026_06_20_130000_create_homepage_configurations_table.php new file mode 100644 index 0000000..9bde298 --- /dev/null +++ b/database/migrations/2026_06_20_130000_create_homepage_configurations_table.php @@ -0,0 +1,23 @@ +id(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + }); + } + + public function down(): void + { + Schema::dropIfExists('homepage_configurations'); + } +}; diff --git a/database/migrations/2026_06_20_235900_create_product_prices_table.php b/database/migrations/2026_06_20_235900_create_product_prices_table.php new file mode 100644 index 0000000..4634192 --- /dev/null +++ b/database/migrations/2026_06_20_235900_create_product_prices_table.php @@ -0,0 +1,33 @@ +id(); + + $table->foreignId('variant_id')->constrained('product_variants')->cascadeOnDelete(); + + $table->enum('type', PriceType::values()); + $table->unsignedInteger('price'); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + + $table->unique(['variant_id', 'type']); + $table->index('variant_id'); + }); + } + + public function down(): void + { + Schema::dropIfExists('product_prices'); + } +}; diff --git a/database/migrations/2026_06_26_100000_create_owner_verification_requests_table.php b/database/migrations/2026_06_26_100000_create_owner_verification_requests_table.php new file mode 100644 index 0000000..9ddebd1 --- /dev/null +++ b/database/migrations/2026_06_26_100000_create_owner_verification_requests_table.php @@ -0,0 +1,31 @@ +id(); + + $table->string('action', 50); + $table->string('status', 50)->default('pending'); + $table->nullableMorphs('subject'); + $table->foreignId('submitted_by_id')->constrained('users')->restrictOnDelete(); + $table->foreignId('verified_by_id')->nullable()->constrained('users')->restrictOnDelete(); + $table->json('payload'); + $table->timestamp('verified_at')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + }); + } + + public function down(): void + { + Schema::dropIfExists('owner_verification_requests'); + } +}; diff --git a/database/migrations/2026_06_27_130700_create_notifications_table.php b/database/migrations/2026_06_27_130700_create_notifications_table.php new file mode 100644 index 0000000..a5b27b3 --- /dev/null +++ b/database/migrations/2026_06_27_130700_create_notifications_table.php @@ -0,0 +1,31 @@ +id(); + + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + + $table->string('title'); + $table->text('body')->nullable(); + $table->string('url')->nullable(); + $table->boolean('is_read')->default(false); + $table->timestamp('read_at')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + }); + } + + public function down(): void + { + Schema::dropIfExists('notifications'); + } +}; diff --git a/database/migrations/2026_06_28_092748_create_employee_advance_payments_table.php b/database/migrations/2026_06_28_092748_create_employee_advance_payments_table.php new file mode 100644 index 0000000..7dd1e07 --- /dev/null +++ b/database/migrations/2026_06_28_092748_create_employee_advance_payments_table.php @@ -0,0 +1,31 @@ +id(); + + $table->foreignId('employee_advance_id')->constrained()->cascadeOnDelete(); + $table->foreignId('paid_by_id')->nullable()->constrained('users')->restrictOnDelete(); + $table->foreignId('cash_transaction_id')->nullable()->unique()->constrained('cash_transactions')->restrictOnDelete(); + + $table->unsignedBigInteger('amount'); + $table->string('description', 100)->nullable(); + $table->timestamp('paid_at')->useCurrent(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + }); + } + + public function down(): void + { + Schema::dropIfExists('employee_advance_payments'); + } +}; diff --git a/database/migrations/2026_06_28_094400_create_retail_stock_histories_table.php b/database/migrations/2026_06_28_094400_create_retail_stock_histories_table.php new file mode 100644 index 0000000..b1da23a --- /dev/null +++ b/database/migrations/2026_06_28_094400_create_retail_stock_histories_table.php @@ -0,0 +1,32 @@ +id(); + + $table->foreignId('product_variant_id')->constrained('product_variants')->cascadeOnDelete(); + $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); + + $table->unsignedInteger('quantity'); + $table->unsignedInteger('stock_before'); + $table->unsignedInteger('retail_stock_before'); + $table->unsignedInteger('stock_after'); + $table->unsignedInteger('retail_stock_after'); + $table->string('notes')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + }); + } + + public function down(): void + { + Schema::dropIfExists('retail_stock_histories'); + } +}; diff --git a/database/migrations/2026_06_28_110000_create_stok_opnames_table.php b/database/migrations/2026_06_28_110000_create_stok_opnames_table.php new file mode 100644 index 0000000..2cda8a7 --- /dev/null +++ b/database/migrations/2026_06_28_110000_create_stok_opnames_table.php @@ -0,0 +1,33 @@ +id(); + + $table->foreignId('created_by_id')->constrained('users')->cascadeOnDelete(); + $table->foreignId('verified_by_id')->nullable()->constrained('users')->nullOnDelete(); + + $table->date('opname_date'); + $table->enum('status', StokOpnameStatus::values())->default(StokOpnameStatus::DRAFT->value); + $table->text('notes')->nullable(); + $table->text('verification_notes')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('stok_opnames'); + } +}; diff --git a/database/migrations/2026_06_28_110001_create_stok_opname_items_table.php b/database/migrations/2026_06_28_110001_create_stok_opname_items_table.php new file mode 100644 index 0000000..37d6bb4 --- /dev/null +++ b/database/migrations/2026_06_28_110001_create_stok_opname_items_table.php @@ -0,0 +1,33 @@ +id(); + + $table->foreignId('stok_opname_id')->constrained('stok_opnames')->cascadeOnDelete(); + $table->foreignId('product_variant_id')->constrained('product_variants')->cascadeOnDelete(); + $table->enum('stock_quality', ProductStockQuality::values())->default(ProductStockQuality::GOOD->value); + + $table->unsignedInteger('system_stock')->default(0); + $table->unsignedInteger('physical_stock')->default(0); + $table->integer('difference')->default(0); + $table->text('notes')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + }); + } + + public function down(): void + { + Schema::dropIfExists('stok_opname_items'); + } +}; diff --git a/database/migrations/2026_07_04_000001_create_cutting_material_combinations_table.php b/database/migrations/2026_07_04_000001_create_cutting_material_combinations_table.php new file mode 100644 index 0000000..6c0af53 --- /dev/null +++ b/database/migrations/2026_07_04_000001_create_cutting_material_combinations_table.php @@ -0,0 +1,28 @@ +id(); + + $table->foreignId('user_id')->nullable()->constrained()->cascadeOnDelete(); + $table->foreignId('cutting_id')->nullable()->constrained()->cascadeOnDelete(); + $table->integer('material_result')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('cutting_material_combinations'); + } +}; diff --git a/database/migrations/2026_07_13_000001_create_restocks_table.php b/database/migrations/2026_07_13_000001_create_restocks_table.php new file mode 100644 index 0000000..978e75f --- /dev/null +++ b/database/migrations/2026_07_13_000001_create_restocks_table.php @@ -0,0 +1,32 @@ +id(); + + $table->foreignId('created_by_id')->constrained('users')->cascadeOnDelete(); + + $table->unsignedBigInteger('subtotal'); + $table->unsignedBigInteger('total'); + $table->string('notes', 100)->nullable(); + $table->enum('stock_type', ProductStockQuality::values())->default(ProductStockQuality::GOOD->value); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('restocks'); + } +}; diff --git a/database/migrations/2026_07_13_000002_create_restock_items_table.php b/database/migrations/2026_07_13_000002_create_restock_items_table.php new file mode 100644 index 0000000..d739cfc --- /dev/null +++ b/database/migrations/2026_07_13_000002_create_restock_items_table.php @@ -0,0 +1,32 @@ +id(); + + $table->foreignId('restock_id')->nullable()->constrained()->cascadeOnDelete(); + $table->foreignId('user_id')->nullable()->constrained()->cascadeOnDelete(); + $table->foreignId('product_variant_id')->constrained()->cascadeOnDelete(); + + $table->integer('quantity'); + $table->unsignedBigInteger('unit_price'); + $table->unsignedBigInteger('subtotal'); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('restock_items'); + } +}; diff --git a/database/migrations/2026_07_27_000001_create_stock_mutations_table.php b/database/migrations/2026_07_27_000001_create_stock_mutations_table.php new file mode 100644 index 0000000..4101a6d --- /dev/null +++ b/database/migrations/2026_07_27_000001_create_stock_mutations_table.php @@ -0,0 +1,34 @@ +id(); + + $table->foreignId('user_id')->constrained(); + + $table->morphs('stockable'); + $table->string('type'); + $table->nullableMorphs('source'); + $table->decimal('quantity', 18, 4); + $table->decimal('stock_before', 18, 4); + $table->decimal('stock_after', 18, 4); + $table->string('stock_quality')->nullable(); + $table->string('description')->nullable(); + + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + }); + } + + public function down(): void + { + Schema::dropIfExists('stock_mutations'); + } +};