feat(voucher): mengaitkan voucher dengan outlet dengan relasi many to many

-membuat tabel pivot
-menyesuaikan seeder
-menyesuaikan logika yang berkaitan dengan perubahan tersebut
This commit is contained in:
Yoga Pangestu 2025-09-30 10:00:54 +07:00
parent 6b74908d45
commit ef05482000
11 changed files with 133 additions and 7 deletions

View File

@ -10,6 +10,7 @@
use Illuminate\Support\Facades\Blade;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelLivewireTables\Views\Columns\ArrayColumn;
class VouchersTable extends DataTableComponent
{
@ -54,6 +55,11 @@ public function columns(): array
Column::make('Batas Per Pembali', 'limit_per_user')->searchable()->sortable(),
ArrayColumn::make('Outlet')
->data(fn ($value, $row) => $row->outlets->pluck('name')->toArray())
->outputFormat(fn ($index, $value) => Blade::render('<flux:badge color="zinc" dot>'.$value.'</flux:badge>'))
->flexRow(['class' => 'gap-2 flex-wrap']),
Column::make('Tanggal')
->label(function ($row) {
$startDate = formatDate($row->start_date);
@ -105,6 +111,6 @@ public function columns(): array
public function builder(): Builder
{
return Voucher::select('id', 'name', 'code', 'min_purchase', 'discount_amount', 'max_discount', 'quota', 'limit_per_user', 'start_date', 'end_date', 'is_active', 'type');
return Voucher::select('id', 'name', 'code', 'min_purchase', 'discount_amount', 'max_discount', 'quota', 'limit_per_user', 'start_date', 'end_date', 'is_active', 'type')->with('outlets');
}
}

View File

@ -6,6 +6,7 @@
use App\Enums\VoucherType;
use App\Models\Voucher;
use App\Rules\UnsignedInteger;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Livewire\Form;
@ -37,6 +38,8 @@ class VoucherForm extends Form
public string $is_active = '1';
public array $outlet_ids = [];
public function rules(): array
{
return [
@ -71,6 +74,8 @@ public function rules(): array
'end_date' => ['nullable', 'date', 'after_or_equal:start_date'],
'terms_conditions' => ['nullable', 'string'],
'is_active' => ['required', Rule::in(IsActive::values())],
'outlet_ids' => ['required', 'array', 'min:1'],
'outlet_ids.*' => Rule::exists('outlets', 'id'),
];
}
@ -94,6 +99,8 @@ public function validationAttributes(): array
public function setVoucher(Voucher $voucher)
{
$voucher->load('outlets');
$this->voucher = $voucher;
$this->name = $this->voucher->name;
@ -108,6 +115,8 @@ public function setVoucher(Voucher $voucher)
$this->start_date = $this->voucher->start_date;
$this->end_date = $this->voucher->end_date;
$this->is_active = $this->voucher->is_active->value;
$this->outlet_ids = $this->voucher->outlets->pluck('id')->toArray();
}
public function store()
@ -116,7 +125,11 @@ public function store()
$data = $this->prepareSavedData();
Voucher::create($data);
DB::transaction(function () use ($data) {
$voucher = Voucher::create($data);
$voucher->outlets()->attach($this->outlet_ids);
});
}
public function update()
@ -124,8 +137,11 @@ public function update()
$this->validate();
$data = $this->prepareSavedData();
DB::transaction(function () use ($data) {
$this->voucher->update($data);
$this->voucher->update($data);
$this->voucher->outlets()->sync($this->outlet_ids);
});
}
private function prepareSavedData()

View File

@ -3,6 +3,8 @@
namespace App\Livewire\Studio\Loyalty\Voucher;
use App\Livewire\Forms\VoucherForm;
use App\Models\Outlet;
use App\Traits\Voucher\WithOutletSelector;
use App\Traits\WithUpdatedData;
use Flux\Flux;
use Livewire\Attributes\Title;
@ -11,10 +13,17 @@
#[Title('Tambah Voucher')]
class Create extends Component
{
use WithUpdatedData;
use WithOutletSelector, WithUpdatedData;
public VoucherForm $form;
public array $outlets;
public function mount()
{
$this->outlets = Outlet::pluck('name', 'id')->toArray();
}
public function save()
{
$this->form->store();

View File

@ -3,7 +3,9 @@
namespace App\Livewire\Studio\Loyalty\Voucher;
use App\Livewire\Forms\VoucherForm;
use App\Models\Outlet;
use App\Models\Voucher;
use App\Traits\Voucher\WithOutletSelector;
use App\Traits\WithUpdatedData;
use Flux\Flux;
use Livewire\Attributes\Title;
@ -12,13 +14,17 @@
#[Title('Ubah Voucher')]
class Edit extends Component
{
use WithUpdatedData;
use WithOutletSelector, WithUpdatedData;
public VoucherForm $form;
public array $outlets;
public function mount(Voucher $voucher)
{
$this->form->setVoucher($voucher);
$this->outlets = Outlet::pluck('name', 'id')->toArray();
}
public function save()

View File

@ -47,4 +47,9 @@ public function facilities(): HasMany
{
return $this->hasMany(Facility::class);
}
public function vouchers(): BelongsToMany
{
return $this->belongsToMany(Voucher::class);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class OutletVoucher extends Model
{
protected $table = 'outlet_voucher';
protected $guarded = ['id'];
}

View File

@ -6,6 +6,7 @@
use App\Enums\VoucherType;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Voucher extends Model
@ -26,4 +27,9 @@ protected function casts(): array
'is_active' => IsActive::class,
];
}
public function outlets(): BelongsToMany
{
return $this->belongsToMany(Outlet::class);
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Traits\Voucher;
trait WithOutletSelector
{
public function selectAll()
{
$this->form->outlet_ids = array_keys($this->outlets);
}
public function deselectAll()
{
$this->form->outlet_ids = [];
}
}

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('outlet_voucher', function (Blueprint $table) {
$table->id();
$table->foreignId('outlet_id')->constrained()->cascadeOnDelete();
$table->foreignId('voucher_id')->constrained()->cascadeOnDelete();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('outlet_voucher');
}
};

View File

@ -4,6 +4,7 @@
use App\Enums\IsActive;
use App\Enums\VoucherType;
use App\Models\Outlet;
use App\Models\Voucher;
use Illuminate\Database\Seeder;
@ -11,7 +12,7 @@ class VoucherSeeder extends Seeder
{
public function run(): void
{
Voucher::insert([
$vouchers = [
[
'name' => 'Diskon 10%',
'code' => 'DISC10',
@ -103,6 +104,12 @@ public function run(): void
'end_date' => null,
'is_active' => IsActive::ACTIVE,
],
]);
];
foreach ($vouchers as $voucher) {
$newVoucher = Voucher::create($voucher);
$newVoucher->outlets()->attach(Outlet::inRandomOrder()->first()->id);
}
}
}

View File

@ -125,6 +125,20 @@ class="**:data-[slot=content]:min-h-[100px]!" />
@endforeach
</flux:radio.group>
</flux:card>
<flux:card class="space-y-6 p-6">
<flux:select variant="listbox" multiple searchable placeholder="Pilih Outlet" label="Outlet"
wire:model.live.debounce.500ms="form.outlet_ids">
<flux:select.option wire:click="selectAll" wire:ignore>Pilih Semua
</flux:select.option>
<flux:select.option wire:click="deselectAll" wire:ignore>Hapus Semua
</flux:select.option>
@foreach ($outlets as $key => $value)
<flux:select.option value="{{ $key }}">{{ $value }}
</flux:select.option>
@endforeach
</flux:select>
</flux:card>
</div>
</div>