From d239d580299ed8c562c669cd69dd4684f21d1bbc Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Sat, 27 Dec 2025 17:57:39 +0700 Subject: [PATCH] feat(warehouse): implementasi crud gudang --- .../Datatable/Manage/WarehousesTable.php | 53 +++++++++++ .../Forms/Studio/Master/WarehouseForm.php | 59 ++++++++++++ app/Livewire/Studio/Master/Warehouse.php | 89 +++++++++++++++++++ app/Models/Warehouse.php | 45 ++++++++++ app/Providers/ViewServiceProvider.php | 14 +++ database/factories/WarehouseFactory.php | 27 ++++++ ...5_12_27_100000_create_warehouses_table.php | 31 +++++++ database/seeders/DatabaseSeeder.php | 2 + database/seeders/RolePermissionSeeder.php | 10 +++ database/seeders/WarehouseSeeder.php | 20 +++++ .../studio/master/warehouse.blade.php | 53 +++++++++++ routes/pages/studio.php | 5 ++ 12 files changed, 408 insertions(+) create mode 100644 app/Livewire/Datatable/Manage/WarehousesTable.php create mode 100644 app/Livewire/Forms/Studio/Master/WarehouseForm.php create mode 100644 app/Livewire/Studio/Master/Warehouse.php create mode 100644 app/Models/Warehouse.php create mode 100644 database/factories/WarehouseFactory.php create mode 100644 database/migrations/2025_12_27_100000_create_warehouses_table.php create mode 100644 database/seeders/WarehouseSeeder.php create mode 100644 resources/views/livewire/studio/master/warehouse.blade.php diff --git a/app/Livewire/Datatable/Manage/WarehousesTable.php b/app/Livewire/Datatable/Manage/WarehousesTable.php new file mode 100644 index 0000000..91363d4 --- /dev/null +++ b/app/Livewire/Datatable/Manage/WarehousesTable.php @@ -0,0 +1,53 @@ +searchable()->sortable(), + + Column::make('Aksi') + ->label(function ($row) { + $actions = ''; + + if (auth()->user()->can('update warehouse')) { + $actions .= view('components.actions.table.edit-modal', [ + 'id' => $row->hash, + 'method' => 'update', + 'modalTitle' => 'Ubah Gudang', + ])->render(); + } + + if (auth()->user()->can('delete warehouse')) { + $actions .= view('components.actions.table.delete', [ + 'id' => $row->hash, + ])->render(); + } + + return $actions; + }) + ->html() + ->hideIf(auth()->user()->cannot('update warehouse') && auth()->user()->cannot('delete warehouse')), + ]; + } + + public function builder(): Builder + { + return Warehouse::select('id', 'name'); + } +} diff --git a/app/Livewire/Forms/Studio/Master/WarehouseForm.php b/app/Livewire/Forms/Studio/Master/WarehouseForm.php new file mode 100644 index 0000000..5909af5 --- /dev/null +++ b/app/Livewire/Forms/Studio/Master/WarehouseForm.php @@ -0,0 +1,59 @@ +warehouse = $warehouse; + $this->name = $warehouse->name; + $this->description = $warehouse->description; + } + + public function rules(): array + { + return [ + 'name' => ['required', 'string', 'max:100'], + 'description' => 'nullable', + ]; + } + + public function validationAttributes(): array + { + return [ + 'name' => 'nama', + 'description' => 'keterangan', + ]; + } + + public function store(): void + { + $this->validate(); + + Warehouse::create($this->except('warehouse')); + + $this->reset(); + } + + public function update(): void + { + $this->validate(); + + $this->warehouse->update($this->except('warehouse')); + } + + public function delete(): void + { + $this->warehouse->delete(); + } +} diff --git a/app/Livewire/Studio/Master/Warehouse.php b/app/Livewire/Studio/Master/Warehouse.php new file mode 100644 index 0000000..d222276 --- /dev/null +++ b/app/Livewire/Studio/Master/Warehouse.php @@ -0,0 +1,89 @@ +canOrAbort('create warehouse'); + + $this->form->store(); + + $this->dispatch('refreshDatatable'); + + $this->toast('Gudang berhasil ditambahkan.'); + + Flux::modals()->close(); + } + + public function update(): void + { + $this->canOrAbort('update warehouse'); + + $this->form->update(); + + $this->dispatch('refreshDatatable'); + + $this->toast('Gudang berhasil diperbarui.'); + + Flux::modals()->close(); + } + + public function delete(WarehouseModel $warehouse): void + { + $this->canOrAbort('delete warehouse'); + + $this->form->warehouse = $warehouse; + + $this->form->delete(); + + $this->dispatch('refreshDatatable'); + + $this->toast('Gudang berhasil dihapus.'); + + Flux::modals()->close(); + } + + #[On('modal:open')] + public function openModal(string $method, string $modalTitle, ?string $id = null): void + { + $this->resetValidation(); + $this->resetErrorBag(); + + $this->method = $method; + $this->modalTitle = $modalTitle; + + $id && $this->form->setWarehouse(WarehouseModel::byHashOrFail($id)); + } + + public function render(): View + { + return view('livewire.studio.master.warehouse', [ + 'pageTitle' => 'Gudang', + ]); + } +} diff --git a/app/Models/Warehouse.php b/app/Models/Warehouse.php new file mode 100644 index 0000000..b5279af --- /dev/null +++ b/app/Models/Warehouse.php @@ -0,0 +1,45 @@ +belongsToMany(Perfume::class)->withPivot('stock'); + } + + public function products(): BelongsToMany + { + return $this->belongsToMany(Product::class)->withPivot('stock'); + } + + public function bottles(): BelongsToMany + { + return $this->belongsToMany(Bottle::class)->withPivot('stock'); + } + + public function purchases(): HasMany + { + return $this->hasMany(Purchase::class); + } + + public function restocks(): HasMany + { + return $this->hasMany(Restock::class); + } +} diff --git a/app/Providers/ViewServiceProvider.php b/app/Providers/ViewServiceProvider.php index ff44a76..2e1cd01 100644 --- a/app/Providers/ViewServiceProvider.php +++ b/app/Providers/ViewServiceProvider.php @@ -58,6 +58,13 @@ public function boot(): void 'match' => 'studio.master.outlet.*', 'can' => 'view outlet', ], + [ + 'label' => 'Gudang', + 'icon' => 'building-office-2', + 'route' => 'studio.master.warehouse.index', + 'match' => 'studio.master.warehouse.*', + 'can' => 'view warehouse', + ], [ 'label' => 'Pegawai', 'icon' => 'users', @@ -190,6 +197,13 @@ public function boot(): void 'match' => 'studio.manage.purchase.*', 'can' => 'view purchase', ], + [ + 'label' => 'Restock Toko', + 'icon' => 'arrows-right-left', + 'route' => 'studio.manage.restock.index', + 'match' => 'studio.manage.restock.*', + 'can' => 'view restock', + ], [ 'label' => 'Stock Opname', 'icon' => 'clipboard-document-check', diff --git a/database/factories/WarehouseFactory.php b/database/factories/WarehouseFactory.php new file mode 100644 index 0000000..d40e59b --- /dev/null +++ b/database/factories/WarehouseFactory.php @@ -0,0 +1,27 @@ + + */ +class WarehouseFactory extends Factory +{ + protected $model = Warehouse::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->company().' Warehouse', + 'description' => fake()->optional()->sentence(), + ]; + } +} diff --git a/database/migrations/2025_12_27_100000_create_warehouses_table.php b/database/migrations/2025_12_27_100000_create_warehouses_table.php new file mode 100644 index 0000000..0626862 --- /dev/null +++ b/database/migrations/2025_12_27_100000_create_warehouses_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('name', 100); + $table->text('description')->nullable(); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('warehouses'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index ac69966..e216150 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -11,6 +11,7 @@ public function run(): void if (app()->environment(['staging', 'production'])) { $this->call([ RolePermissionSeeder::class, + WarehouseSeeder::class, OutletSeeder::class, UserSeeder::class, TierSeeder::class, @@ -19,6 +20,7 @@ public function run(): void $this->call([ RolePermissionSeeder::class, OutletSeeder::class, + WarehouseSeeder::class, UserSeeder::class, TierSeeder::class, VoucherSeeder::class, diff --git a/database/seeders/RolePermissionSeeder.php b/database/seeders/RolePermissionSeeder.php index ffc3ebc..faab3d9 100644 --- a/database/seeders/RolePermissionSeeder.php +++ b/database/seeders/RolePermissionSeeder.php @@ -146,6 +146,16 @@ public function run(): void 'create broadcast', 'delete broadcast', + 'view warehouse', + 'create warehouse', + 'update warehouse', + 'delete warehouse', + + 'view restock', + 'create restock', + 'update restock', + 'delete restock', + 'view perfume feature', 'create perfume feature', 'update perfume feature', diff --git a/database/seeders/WarehouseSeeder.php b/database/seeders/WarehouseSeeder.php new file mode 100644 index 0000000..bfaa360 --- /dev/null +++ b/database/seeders/WarehouseSeeder.php @@ -0,0 +1,20 @@ + 'Gudang Pusat', + 'description' => 'Gudang utama untuk penyimpanan semua produk.', + ]); + } +} diff --git a/resources/views/livewire/studio/master/warehouse.blade.php b/resources/views/livewire/studio/master/warehouse.blade.php new file mode 100644 index 0000000..d28767b --- /dev/null +++ b/resources/views/livewire/studio/master/warehouse.blade.php @@ -0,0 +1,53 @@ + +
+
+ {{ $pageTitle }} +
+ @can('create warehouse') + + + Tambah + + + @endcan +
+ +
+ +
+ + @include('components.modals.confirmation', [ + 'modalName' => 'delete-confirmation', + 'modalTitle' => 'Apakah Anda yakin?', + 'modalMessage' => 'Data yang berelasi dengan data ini juga akan ikut terhapus.', + 'buttonVariant' => 'primary', + 'buttonColor' => 'danger', + 'buttonText' => 'Ya, Hapus', + ]) + + +
+ {{ $modalTitle }} + + + Nama * + + + + + + Deskripsi + + + + +
+ + + Simpan + +
+
+
+
diff --git a/routes/pages/studio.php b/routes/pages/studio.php index b5e5c88..df8f195 100644 --- a/routes/pages/studio.php +++ b/routes/pages/studio.php @@ -50,6 +50,7 @@ use App\Livewire\Studio\Master\User\Create as UserCreate; use App\Livewire\Studio\Master\User\Edit as UserEdit; use App\Livewire\Studio\Master\User\Index as UserIndex; +use App\Livewire\Studio\Master\Warehouse as WarehouseComponent; use App\Livewire\Studio\Setting\Account as AccountComponent; use App\Livewire\Studio\Setting\Application as ApplicationComponent; use Illuminate\Support\Facades\Route; @@ -72,6 +73,10 @@ Route::get('/{outlet}/edit', OutletEdit::class)->name('edit')->middleware('can:update outlet'); }); + Route::prefix('warehouse')->name('warehouse.')->group(function () { + Route::get('/', WarehouseComponent::class)->name('index')->middleware('can:view warehouse'); + }); + Route::prefix('users')->name('user.')->group(function () { Route::get('/', UserIndex::class)->name('index')->middleware('can:view user'); Route::get('/create', UserCreate::class)->name('create')->middleware('can:create user');