feat(price request): membuat enum status

- membuat datatables
- membuat logika untuk handle approve dan reject
- casting dan scope model
- membuat badge di sidebar untuk counting data yang belum disetujui
- menyesuaikan logika untuk mengajukan harga beli dengan skema table yang baru
- menyesuaikan skema table yang baru
- menyesuaikan parameter yang dikriim untuk pengajuan
- menyesuaikan logika table
- membuat permission
This commit is contained in:
Yoga Pangestu 2025-11-21 14:58:49 +07:00
parent c984206d94
commit 396440eda7
16 changed files with 357 additions and 122 deletions

View File

@ -0,0 +1,33 @@
<?php
namespace App\Enums;
use App\Traits\WithCommentEnum;
use App\Traits\WithValueEnum;
enum PriceRequestStatus: int
{
use WithCommentEnum, WithValueEnum;
case PENDING = 1;
case APPROVED = 2;
case REJECTED = 3;
public function label(): string
{
return match ($this) {
self::PENDING => 'Menunggu',
self::APPROVED => 'Disetujui',
self::REJECTED => 'Ditolak',
};
}
public function color()
{
return match ($this) {
self::PENDING => 'gray',
self::APPROVED => 'emerald',
self::REJECTED => 'red',
};
}
}

View File

@ -21,10 +21,16 @@ class BottlesTable extends DataTableComponent
protected $model = Bottle::class; protected $model = Bottle::class;
protected ?int $refreshTime = 3000; protected int $refreshTime = 3000;
public function columns(): array public function columns(): array
{ {
$canSeePrice = PriceRequest::where('user_id', auth()->id())
->where('module', 'botol')
->approved()
->where('finalized_at', '>', now()->subMinute(10))
->exists();
return [ return [
Column::make('Nama') Column::make('Nama')
->label(function ($row) { ->label(function ($row) {
@ -42,13 +48,7 @@ public function columns(): array
->html(), ->html(),
Column::make('Harga Beli', 'cost_price') Column::make('Harga Beli', 'cost_price')
->label(function ($row) { ->label(function ($row) use ($canSeePrice) {
$canSeePrice = PriceRequest::where('user_id', auth()->id())
->where('model', Bottle::class)
->whereNotNull('approved_at')
->where('expires_at', '>', now())
->exists();
if ($canSeePrice) { if ($canSeePrice) {
return currency($row->cost_price, 'Rp'); return currency($row->cost_price, 'Rp');
} }
@ -69,22 +69,22 @@ public function columns(): array
->hideIf(! auth()->user()->hasRole(['Developer', 'Owner'])), ->hideIf(! auth()->user()->hasRole(['Developer', 'Owner'])),
Column::make('Harga Jual', 'sale_price') Column::make('Harga Jual', 'sale_price')
->label(fn ($row, $column) => currency($row->sale_price, 'Rp')) ->label(fn($row, $column) => currency($row->sale_price, 'Rp'))
->searchable() ->searchable()
->sortable(), ->sortable(),
ImageColumn::make('Gambar') ImageColumn::make('Gambar')
->location(fn ($row) => optional($row->getMedia('image')->first())->getUrl() ?? asset('assets/images/logo.png')) ->location(fn($row) => optional($row->getMedia('image')->first())->getUrl() ?? asset('assets/images/logo.png'))
->attributes(fn ($row) => [ ->attributes(fn($row) => [
'style' => 'width: 50px; height: 50px;', 'style' => 'width: 50px; height: 50px;',
'alt' => $row->name, 'alt' => $row->name,
]), ]),
ArrayColumn::make('Outlet') ArrayColumn::make('Outlet')
->data( ->data(
fn ($value, $row) => $row->outlets fn($value, $row) => $row->outlets
->filter(fn ($outlet) => auth()->user()->outlets->pluck('id')->contains($outlet->id)) ->filter(fn($outlet) => auth()->user()->outlets->pluck('id')->contains($outlet->id))
->map(fn ($outlet) => [ ->map(fn($outlet) => [
'id' => $outlet->hash, 'id' => $outlet->hash,
'bottle_id' => $row->hash, 'bottle_id' => $row->hash,
'name' => $outlet->name, 'name' => $outlet->name,
@ -96,7 +96,7 @@ public function columns(): array
$canAudit = auth()->user()->can('audit bottle'); $canAudit = auth()->user()->can('audit bottle');
$tag = $canAudit ? 'div' : 'a'; $tag = $canAudit ? 'div' : 'a';
$attrs = $canAudit ? '' : 'href="'.$route.'" wire:navigate'; $attrs = $canAudit ? '' : 'href="' . $route . '" wire:navigate';
return Blade::render(" return Blade::render("
<{$tag} {$attrs} <{$tag} {$attrs}
@ -142,7 +142,7 @@ public function builder(): Builder
->with('outlets') ->with('outlets')
->whereHas( ->whereHas(
'outlets', 'outlets',
fn ($q) => $q->whereIn('outlets.id', auth()->user()->outlets()->pluck('outlets.id')) fn($q) => $q->whereIn('outlets.id', auth()->user()->outlets()->pluck('outlets.id'))
); );
} }
} }

View File

@ -20,10 +20,16 @@ class PerfumesTable extends DataTableComponent
protected $model = Perfume::class; protected $model = Perfume::class;
protected ?int $refreshTime = 3000; protected $refreshTime = 3000;
public function columns(): array public function columns(): array
{ {
$canSeePrice = PriceRequest::where('user_id', auth()->id())
->where('module', 'parfum')
->approved()
->where('finalized_at', '>', now()->subMinute(10))
->exists();
return [ return [
Column::make('Nama') Column::make('Nama')
->label(function ($row) { ->label(function ($row) {
@ -41,24 +47,18 @@ public function columns(): array
->html(), ->html(),
ArrayColumn::make('Kategori') ArrayColumn::make('Kategori')
->data(fn ($value, $row) => $row->categories->pluck('name')->toArray()) ->data(fn($value, $row) => $row->categories->pluck('name')->toArray())
->outputFormat(fn ($index, $value) => Blade::render('<span class="text-xs text-gray-400 border border-gray-400 rounded px-1">'.$value.'</span>')) ->outputFormat(fn($index, $value) => Blade::render('<span class="text-xs text-gray-400 border border-gray-400 rounded px-1">' . $value . '</span>'))
->flexRow(['class' => 'gap-2 flex-wrap']), ->flexRow(['class' => 'gap-2 flex-wrap']),
Column::make('Merek', 'brand.name')->searchable()->sortable(), Column::make('Merek', 'brand.name')->searchable()->sortable(),
Column::make('Concentration', 'concentration') Column::make('Concentration', 'concentration')
->label(fn ($row, $column) => Blade::render('<flux:badge color="'.$row->concentration->color().'">'.$row->concentration->label().'</flux:badge>')) ->label(fn($row, $column) => Blade::render('<flux:badge color="' . $row->concentration->color() . '">' . $row->concentration->label() . '</flux:badge>'))
->html(), ->html(),
Column::make('Harga Beli', 'cost_price') Column::make('Harga Beli', 'cost_price')
->label(function ($row) { ->label(function ($row) use ($canSeePrice) {
$canSeePrice = PriceRequest::where('user_id', auth()->id())
->where('model', Perfume::class)
->whereNotNull('approved_at')
->where('expires_at', '>', now())
->exists();
if ($canSeePrice) { if ($canSeePrice) {
return currency($row->cost_price, 'Rp'); return currency($row->cost_price, 'Rp');
} }
@ -79,15 +79,15 @@ public function columns(): array
->hideIf(! auth()->user()->hasRole(['Developer', 'Owner'])), ->hideIf(! auth()->user()->hasRole(['Developer', 'Owner'])),
Column::make('Harga Jual', 'sale_price') Column::make('Harga Jual', 'sale_price')
->label(fn ($row, $column) => currency($row->sale_price, 'Rp')) ->label(fn($row, $column) => currency($row->sale_price, 'Rp'))
->searchable() ->searchable()
->sortable(), ->sortable(),
ArrayColumn::make('Outlet') ArrayColumn::make('Outlet')
->data( ->data(
fn ($value, $row) => $row->outlets fn($value, $row) => $row->outlets
->filter(fn ($outlet) => auth()->user()->outlets->pluck('id')->contains($outlet->id)) ->filter(fn($outlet) => auth()->user()->outlets->pluck('id')->contains($outlet->id))
->map(fn ($outlet) => [ ->map(fn($outlet) => [
'id' => $outlet->hash, 'id' => $outlet->hash,
'perfume_id' => $row->hash, 'perfume_id' => $row->hash,
'name' => $outlet->name, 'name' => $outlet->name,
@ -99,7 +99,7 @@ public function columns(): array
$canAudit = auth()->user()->can('audit perfume'); $canAudit = auth()->user()->can('audit perfume');
$tag = $canAudit ? 'div' : 'a'; $tag = $canAudit ? 'div' : 'a';
$attrs = $canAudit ? '' : 'href="'.$route.'" wire:navigate'; $attrs = $canAudit ? '' : 'href="' . $route . '" wire:navigate';
return Blade::render(" return Blade::render("
<{$tag} {$attrs} <{$tag} {$attrs}
@ -159,7 +159,7 @@ public function builder(): Builder
]) ])
->whereHas( ->whereHas(
'outlets', 'outlets',
fn ($q) => $q->whereIn('outlets.id', auth()->user()->outlets()->pluck('outlets.id')) fn($q) => $q->whereIn('outlets.id', auth()->user()->outlets()->pluck('outlets.id'))
); );
} }
} }

View File

@ -20,10 +20,16 @@ class ProductsTable extends DataTableComponent
protected $model = Product::class; protected $model = Product::class;
protected ?int $refreshTime = 3000; protected int $refreshTime = 3000;
public function columns(): array public function columns(): array
{ {
$canSeePrice = PriceRequest::where('user_id', auth()->id())
->where('module', 'produk')
->approved()
->where('finalized_at', '>', now()->subMinute(10))
->exists();
return [ return [
Column::make('Nama') Column::make('Nama')
->label(function ($row) { ->label(function ($row) {
@ -41,13 +47,7 @@ public function columns(): array
->html(), ->html(),
Column::make('Harga Beli', 'cost_price') Column::make('Harga Beli', 'cost_price')
->label(function ($row) { ->label(function ($row) use ($canSeePrice) {
$canSeePrice = PriceRequest::where('user_id', auth()->id())
->where('model', Product::class)
->whereNotNull('approved_at')
->where('expires_at', '>', now())
->exists();
if ($canSeePrice) { if ($canSeePrice) {
return currency($row->cost_price, 'Rp'); return currency($row->cost_price, 'Rp');
} }
@ -68,15 +68,15 @@ public function columns(): array
->hideIf(! auth()->user()->hasRole(['Developer', 'Owner'])), ->hideIf(! auth()->user()->hasRole(['Developer', 'Owner'])),
Column::make('Harga Jual', 'sale_price') Column::make('Harga Jual', 'sale_price')
->label(fn ($row, $column) => currency($row->sale_price, 'Rp')) ->label(fn($row, $column) => currency($row->sale_price, 'Rp'))
->searchable() ->searchable()
->sortable(), ->sortable(),
ArrayColumn::make('Outlet') ArrayColumn::make('Outlet')
->data( ->data(
fn ($value, $row) => $row->outlets fn($value, $row) => $row->outlets
->filter(fn ($outlet) => auth()->user()->outlets->pluck('id')->contains($outlet->id)) ->filter(fn($outlet) => auth()->user()->outlets->pluck('id')->contains($outlet->id))
->map(fn ($outlet) => [ ->map(fn($outlet) => [
'id' => $outlet->hash, 'id' => $outlet->hash,
'product_id' => $row->hash, 'product_id' => $row->hash,
'name' => $outlet->name, 'name' => $outlet->name,
@ -88,7 +88,7 @@ public function columns(): array
$canAudit = auth()->user()->can('audit product'); $canAudit = auth()->user()->can('audit product');
$tag = $canAudit ? 'div' : 'a'; $tag = $canAudit ? 'div' : 'a';
$attrs = $canAudit ? '' : 'href="'.$route.'" wire:navigate'; $attrs = $canAudit ? '' : 'href="' . $route . '" wire:navigate';
return Blade::render(" return Blade::render("
<{$tag} {$attrs} <{$tag} {$attrs}
@ -134,7 +134,7 @@ public function builder(): Builder
->with('outlets') ->with('outlets')
->whereHas( ->whereHas(
'outlets', 'outlets',
fn ($q) => $q->whereIn('outlets.id', auth()->user()->outlets()->pluck('outlets.id')) fn($q) => $q->whereIn('outlets.id', auth()->user()->outlets()->pluck('outlets.id'))
); );
} }
} }

View File

@ -0,0 +1,106 @@
<?php
namespace App\Livewire\Datatable\Studio\Manage;
use App\Enums\PriceRequestStatus;
use App\Models\PriceRequest;
use App\Traits\Datatable\WithAppendColumn;
use App\Traits\Datatable\WithConfiguration;
use App\Traits\Datatable\WithPrependColumn;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Blade;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
class PriceRequestsTable extends DataTableComponent
{
use WithConfiguration, WithPrependColumn, WithAppendColumn;
protected $model = PriceRequest::class;
public function columns(): array
{
return [
Column::make('Pemohon', 'user.employee.full_name')->searchable(),
Column::make('module', 'module')
->format(fn($row) => ucfirst($row))
->searchable(),
Column::make('Status', 'status')
->label(fn($row, $column) => Blade::render('<flux:badge color="' . $row->status->color() . '">' . $row->status->label() . '</flux:badge>'))
->html(),
Column::make('Tanggal')
->label(function ($row) {
$startDate = formatDate($row->created_at);
$startAgo = timeAgo($row->created_at);
$endDate = formatDate($row->finalized_at);
$endAgo = timeAgo($row->finalized_at);
return <<<HTML
<div class="flex flex-col text-xs text-gray-700 dark:text-gray-300 gap-0.5">
<div>
<span class="font-medium text-gray-900 dark:text-white">Tgl Permohonan:</span>
<span>{$startDate}</span>
<span class="text-gray-500">({$startAgo})</span>
</div>
<div>
<span class="font-medium text-gray-900 dark:text-white">Tgl Ditanggapi:</span>
<span>{$endDate}</span>
<span class="text-gray-500">({$endAgo})</span>
</div>
</div>
HTML;
})
->html(),
Column::make('Aksi')
->label(function ($row) {
$actions = '';
if (auth()->user()->can('approve price request') && $row->status === PriceRequestStatus::PENDING) {
$actions .= view('components.datatables.confirm', [
'id' => $row->hash,
'message' => 'Pemohon yang diterima bisa melihat data harga beli.',
'confirmButtonText' => 'Ya, Terima',
'target' => 'approve',
'title' => 'Terima',
'confirmButtonColor' => 'primary',
'icon' => 'check-circle',
])->render();
}
if (auth()->user()->can('reject price request') && $row->status === PriceRequestStatus::PENDING) {
$actions .= view('components.datatables.confirm', [
'id' => $row->hash,
'message' => 'Jika ditolak, pomohon tidak dapat melihat data harga beli.',
'confirmButtonText' => 'Ya, Tolak',
'target' => 'reject',
'title' => 'Tolak',
'confirmButtonColor' => 'red',
'icon' => 'x-circle',
])->render();
}
return $actions;
})
->html()
->hideIf(auth()->user()->cannot('approve price request') && auth()->user()->cannot('reject price request')),
];
}
public function builder(): Builder
{
return PriceRequest::select(
'price_requests.id',
'price_requests.user_id',
'module',
'price_requests.status',
'price_requests.created_at',
'finalized_at'
)
->with(['user', 'user.employee']);
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace App\Livewire\Studio\Manage;
use App\Enums\PriceRequestStatus;
use App\Jobs\StorePushNotification;
use App\Livewire\Datatable\Studio\Catalog\PerfumesTable;
use App\Models\PriceRequest as PriceRequestModel;
use App\Models\PushNotification;
use App\Traits\Notification\WithSubscribeNotification;
use App\Traits\WithAuthorization;
use App\Traits\WithConfirmation;
use App\Traits\WithToast;
use Flux\Flux;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Title('Permintaan Harga')]
class PriceRequest extends Component
{
use WithAuthorization, WithConfirmation, WithSubscribeNotification, WithToast;
public function approve(PriceRequestModel $priceRequest)
{
$this->canOrAbort('approve price request');
$priceRequest->update([
'status' => PriceRequestStatus::APPROVED->value,
'finalized_at' => now()
]);
StorePushNotification::dispatch(
PushNotification::where('user_id', $priceRequest->user_id)->get(),
[
'title' => 'Permintaan Harga Disetujui ✅',
'body' => "Selamat! Permintaan untuk melihat harga beli {$priceRequest->module} sudah disetujui 🎉",
],
);
$this->dispatch('refreshDatatable');
$this->toast('Permintaan harga berhasil disetujui.');
Flux::modals()->close();
}
public function reject(PriceRequestModel $priceRequest)
{
$this->canOrAbort('reject price request');
$priceRequest->update([
'status' => PriceRequestStatus::REJECTED->value,
'finalized_at' => now()
]);
StorePushNotification::dispatch(
PushNotification::where('user_id', $priceRequest->user_id)->get(),
[
'title' => 'Permintaan Harga Ditolak ❌',
'body' => "Maaf, permintaan untuk melihat harga beli {$priceRequest->module} tidak dapat dipenuhi 🙏",
],
);
$this->dispatch('refreshDatatable');
$this->toast('Permintaan harga berhasil ditolak.');
Flux::modals()->close();
}
public function render()
{
return view('livewire.studio.manage.price-requests', [
'pageTitle' => 'Permintaan Harga',
]);
}
}

View File

@ -2,6 +2,9 @@
namespace App\Models; namespace App\Models;
use App\Enums\PriceRequestStatus;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@ -13,6 +16,32 @@ class PriceRequest extends Model
protected $guarded = ['id']; protected $guarded = ['id'];
protected function casts(): array
{
return [
'status' => PriceRequestStatus::class,
'finalized_at' => 'datetime',
];
}
#[Scope]
protected function pending(Builder $query): void
{
$query->where('status', PriceRequestStatus::PENDING->value);
}
#[Scope]
protected function approved(Builder $query): void
{
$query->where('status', PriceRequestStatus::APPROVED->value);
}
#[Scope]
protected function rejected(Builder $query): void
{
$query->where('status', PriceRequestStatus::REJECTED->value);
}
public function user(): BelongsTo public function user(): BelongsTo
{ {
return $this->belongsTo(User::class); return $this->belongsTo(User::class);

View File

@ -2,6 +2,8 @@
namespace App\Providers; namespace App\Providers;
use App\Enums\PriceRequestStatus;
use App\Models\PriceRequest;
use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
@ -9,7 +11,9 @@ class ViewServiceProvider extends ServiceProvider
{ {
public function boot(): void public function boot(): void
{ {
View::composer('*', function ($view) { $priceRequestCount = PriceRequest::where('status', PriceRequestStatus::PENDING->value)->count();
View::composer('*', function ($view) use ($priceRequestCount) {
$sidebar = [ $sidebar = [
[ [
'heading' => 'Dasbor', 'heading' => 'Dasbor',
@ -165,6 +169,14 @@ public function boot(): void
'match' => 'studio.manage.article.*', 'match' => 'studio.manage.article.*',
'can' => 'view article', 'can' => 'view article',
], ],
[
'label' => 'Permintaan Harga',
'icon' => 'exclamation-triangle',
'route' => 'studio.manage.price_request.index',
'match' => 'studio.manage.price_request.*',
'can' => 'view price request',
'badge' => $priceRequestCount,
],
[ [
'label' => 'FAQs', 'label' => 'FAQs',
'icon' => 'question-mark-circle', 'icon' => 'question-mark-circle',

View File

@ -2,102 +2,57 @@
namespace App\Traits; namespace App\Traits;
use App\Models\Bottle; use App\Jobs\StorePushNotification;
use App\Models\Perfume;
use App\Models\PriceRequest; use App\Models\PriceRequest;
use App\Models\Product; use App\Models\PushNotification;
use App\Models\User; use App\Models\User;
use App\Models\Whatsapp;
use Flux\Flux; use Flux\Flux;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\URL;
trait WithShowPrice trait WithShowPrice
{ {
public function requestPrice(string $module) public function requestPrice(string $module)
{ {
switch ($module) {
case 'perfume':
$label = 'parfum';
$model = Perfume::class;
break;
case 'product':
$label = 'produk';
$model = Product::class;
break;
default:
$label = 'botol';
$model = Bottle::class;
break;
}
$recentRequest = PriceRequest::where('user_id', auth()->id()) $recentRequest = PriceRequest::where('user_id', auth()->id())
->where('model', $model) ->where('module', $module)
->where('created_at', '>', now()->subMinutes(30)) ->pending()
->exists(); ->exists();
if ($recentRequest) { if ($recentRequest) {
$this->toast('Kamu baru saja mengajukan permintaan, tautan persetujuan masih berlaku selama 30 menit.', 'Peringatan', 'warning'); $this->toast('Permohonan kamu sedang di proses oleh owner, mohon tunggu dan kami akan memberi tahu Anda jika prosesnya sudah selesai.', 'Peringatan', 'warning');
Flux::modal('show-price')->close(); Flux::modal('show-price')->close();
return; return;
} }
$whatsapp = Whatsapp::first(); $owner = User::role(['owner', 'Developer'])->with('employee')->first();
$owner = User::role('owner')->with('employee')->first();
if (! $owner) { if (! $owner) {
$this->toast('Owner belum terdaftar, silakan hubungi Admin', 'Gagal', 'danger'); $this->toast('Owner belum terdaftar, silakan hubungi Admin.', 'Gagal', 'danger');
Flux::modal('show-price')->close(); Flux::modal('show-price')->close();
return; return;
} }
$phoneNumber = formatPhoneNumber($owner->employee?->phone_number, '62');
try {
$priceRequest = PriceRequest::create([
'user_id' => auth()->id(),
'model' => $model,
'expires_at' => now()->addMinutes(30),
]);
$signedUrl = URL::temporarySignedRoute(
'price-request.approve',
$priceRequest->expires_at,
['priceRequest' => $priceRequest->hash]
);
$priceRequest->update([
'signature' => hash('sha256', $signedUrl),
]);
$causerName = auth()->user()->employee?->full_name; $causerName = auth()->user()->employee?->full_name;
$response = Http::withHeaders([ DB::transaction(function () use ($module, $owner, $causerName) {
'id' => $whatsapp->zawa_id, PriceRequest::create([
'session-id' => $whatsapp->session_id, 'user_id' => auth()->id(),
'Content-Type' => 'application/json', 'module' => $module,
])->post(config('services.zawa.url').'/message', [
'phone' => $phoneNumber,
'type' => 'text',
'text' => "Haloo {$owner->employee?->full_name}, saya {$causerName}, "
."saya ingin mengajukan permintaan persetujuan untuk mengakses data harga beli {$label}."
."\n\nSilakan klik tautan berikut untuk menyetujui:\n\n{$signedUrl}",
]); ]);
if ($response->successful()) { StorePushNotification::dispatch(
PushNotification::all(),
[
'title' => 'Permintaan Persetujuan Harga Beli',
'body' => "Haloo {$owner->employee?->full_name}, saya {$causerName}, saya ingin mengajukan permintaan persetujuan untuk mengakses data harga beli {$module}.",
'route' => route('studio.manage.price_request.index'),
],
);
});
$this->toast('Permintaan persetujuan berhasil dikirim.'); $this->toast('Permintaan persetujuan berhasil dikirim.');
} else {
$this->toast('Permintaan persetujuan gagal dikirim, silakan coba lagi.', 'Gagal', 'danger');
}
} catch (\Throwable $th) {
Log::error($th->getMessage());
$this->toast('Opps, terjadi kesalahan. Silakan coba lagi dan jika masih terjadi, hubungi Administrator.', 'Gagal', 'danger');
}
Flux::modal('show-price')->close(); Flux::modal('show-price')->close();
} }

View File

@ -1,5 +1,6 @@
<?php <?php
use App\Enums\PriceRequestStatus;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
@ -14,11 +15,9 @@ public function up(): void
Schema::create('price_requests', function (Blueprint $table) { Schema::create('price_requests', function (Blueprint $table) {
$table->id(); $table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('model', 20); $table->string('module', 20);
$table->string('signature')->unique()->nullable(); $table->enum('status', [PriceRequestStatus::values()])->default(PriceRequestStatus::PENDING)->comment(PriceRequestStatus::comment());
$table->timestamp('last_requested_at')->nullable(); $table->timestamp('finalized_at')->nullable();
$table->timestamp('expires_at')->nullable();
$table->timestamp('approved_at')->nullable();
$table->timestamp('created_at')->useCurrent(); $table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate();
$table->softDeletes(); $table->softDeletes();

View File

@ -118,6 +118,10 @@ public function run(): void
'update article', 'update article',
'delete article', 'delete article',
'view price request',
'approve price request',
'reject price request',
'view faq', 'view faq',
'create faq', 'create faq',
'update faq', 'update faq',

View File

@ -26,5 +26,5 @@ class="text-sm">
</div> </div>
@include('components.confirmation.delete') @include('components.confirmation.delete')
@include('components.confirmation.show-price', ['module' => 'bottle']) @include('components.confirmation.show-price', ['module' => 'botol'])
</flux:main> </flux:main>

View File

@ -26,5 +26,5 @@ class="text-sm">
</div> </div>
@include('components.confirmation.delete') @include('components.confirmation.delete')
@include('components.confirmation.show-price', ['module' => 'perfume']) @include('components.confirmation.show-price', ['module' => 'parfum'])
</flux:main> </flux:main>

View File

@ -26,5 +26,5 @@ class="text-sm">
</div> </div>
@include('components.confirmation.delete') @include('components.confirmation.delete')
@include('components.confirmation.show-price', ['module' => 'product']) @include('components.confirmation.show-price', ['module' => 'produk'])
</flux:main> </flux:main>

View File

@ -0,0 +1,13 @@
<flux:main>
<div class="flex justify-between items-center">
<div>
<flux:heading size="xl">{{ $pageTitle }}</flux:heading>
</div>
</div>
<div class="mt-6">
<livewire:datatable.studio.manage.price-requests-table />
</div>
@include('components.confirmation')
</flux:main>

View File

@ -33,6 +33,7 @@
use App\Livewire\Studio\Manage\Order\Create as OrderCreate; use App\Livewire\Studio\Manage\Order\Create as OrderCreate;
use App\Livewire\Studio\Manage\Order\Index as OrderIndex; use App\Livewire\Studio\Manage\Order\Index as OrderIndex;
use App\Livewire\Studio\Manage\Order\Show as OrderShow; use App\Livewire\Studio\Manage\Order\Show as OrderShow;
use App\Livewire\Studio\Manage\PriceRequest as PriceRequestComponent;
use App\Livewire\Studio\Manage\Purchase\Create as PurchaseCreate; use App\Livewire\Studio\Manage\Purchase\Create as PurchaseCreate;
use App\Livewire\Studio\Manage\Purchase\Index as PurchaseIndex; use App\Livewire\Studio\Manage\Purchase\Index as PurchaseIndex;
use App\Livewire\Studio\Master\Formula as FormulaComponent; use App\Livewire\Studio\Master\Formula as FormulaComponent;
@ -176,6 +177,12 @@
Route::get('/', FaqComponent::class)->name('index')->middleware('can:view faq'); Route::get('/', FaqComponent::class)->name('index')->middleware('can:view faq');
Route::delete('/{faq}/delete', FaqComponent::class)->name('delete')->middleware('can:delete faq'); Route::delete('/{faq}/delete', FaqComponent::class)->name('delete')->middleware('can:delete faq');
}); });
Route::prefix('price-requests')->name('price_request.')->group(function () {
Route::get('/', PriceRequestComponent::class)->name('index')->middleware('can:view price request');
Route::patch('/{priceRequest}/approve', PriceRequestComponent::class)->name('approve')->middleware('can:approve price request');
Route::patch('/{priceRequest}/reject', PriceRequestComponent::class)->name('reject')->middleware('can:reject price request');
});
}); });
Route::prefix('feature')->name('studio.feature.')->group(function () { Route::prefix('feature')->name('studio.feature.')->group(function () {