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:
parent
c984206d94
commit
396440eda7
33
app/Enums/PriceRequestStatus.php
Normal file
33
app/Enums/PriceRequestStatus.php
Normal 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',
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -21,10 +21,16 @@ class BottlesTable extends DataTableComponent
|
||||
|
||||
protected $model = Bottle::class;
|
||||
|
||||
protected ?int $refreshTime = 3000;
|
||||
protected int $refreshTime = 3000;
|
||||
|
||||
public function columns(): array
|
||||
{
|
||||
$canSeePrice = PriceRequest::where('user_id', auth()->id())
|
||||
->where('module', 'botol')
|
||||
->approved()
|
||||
->where('finalized_at', '>', now()->subMinute(10))
|
||||
->exists();
|
||||
|
||||
return [
|
||||
Column::make('Nama')
|
||||
->label(function ($row) {
|
||||
@ -42,13 +48,7 @@ public function columns(): array
|
||||
->html(),
|
||||
|
||||
Column::make('Harga Beli', 'cost_price')
|
||||
->label(function ($row) {
|
||||
$canSeePrice = PriceRequest::where('user_id', auth()->id())
|
||||
->where('model', Bottle::class)
|
||||
->whereNotNull('approved_at')
|
||||
->where('expires_at', '>', now())
|
||||
->exists();
|
||||
|
||||
->label(function ($row) use ($canSeePrice) {
|
||||
if ($canSeePrice) {
|
||||
return currency($row->cost_price, 'Rp');
|
||||
}
|
||||
@ -69,22 +69,22 @@ public function columns(): array
|
||||
->hideIf(! auth()->user()->hasRole(['Developer', 'Owner'])),
|
||||
|
||||
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()
|
||||
->sortable(),
|
||||
|
||||
ImageColumn::make('Gambar')
|
||||
->location(fn ($row) => optional($row->getMedia('image')->first())->getUrl() ?? asset('assets/images/logo.png'))
|
||||
->attributes(fn ($row) => [
|
||||
->location(fn($row) => optional($row->getMedia('image')->first())->getUrl() ?? asset('assets/images/logo.png'))
|
||||
->attributes(fn($row) => [
|
||||
'style' => 'width: 50px; height: 50px;',
|
||||
'alt' => $row->name,
|
||||
]),
|
||||
|
||||
ArrayColumn::make('Outlet')
|
||||
->data(
|
||||
fn ($value, $row) => $row->outlets
|
||||
->filter(fn ($outlet) => auth()->user()->outlets->pluck('id')->contains($outlet->id))
|
||||
->map(fn ($outlet) => [
|
||||
fn($value, $row) => $row->outlets
|
||||
->filter(fn($outlet) => auth()->user()->outlets->pluck('id')->contains($outlet->id))
|
||||
->map(fn($outlet) => [
|
||||
'id' => $outlet->hash,
|
||||
'bottle_id' => $row->hash,
|
||||
'name' => $outlet->name,
|
||||
@ -96,7 +96,7 @@ public function columns(): array
|
||||
$canAudit = auth()->user()->can('audit bottle');
|
||||
|
||||
$tag = $canAudit ? 'div' : 'a';
|
||||
$attrs = $canAudit ? '' : 'href="'.$route.'" wire:navigate';
|
||||
$attrs = $canAudit ? '' : 'href="' . $route . '" wire:navigate';
|
||||
|
||||
return Blade::render("
|
||||
<{$tag} {$attrs}
|
||||
@ -142,7 +142,7 @@ public function builder(): Builder
|
||||
->with('outlets')
|
||||
->whereHas(
|
||||
'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'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,10 +20,16 @@ class PerfumesTable extends DataTableComponent
|
||||
|
||||
protected $model = Perfume::class;
|
||||
|
||||
protected ?int $refreshTime = 3000;
|
||||
protected $refreshTime = 3000;
|
||||
|
||||
public function columns(): array
|
||||
{
|
||||
$canSeePrice = PriceRequest::where('user_id', auth()->id())
|
||||
->where('module', 'parfum')
|
||||
->approved()
|
||||
->where('finalized_at', '>', now()->subMinute(10))
|
||||
->exists();
|
||||
|
||||
return [
|
||||
Column::make('Nama')
|
||||
->label(function ($row) {
|
||||
@ -41,24 +47,18 @@ public function columns(): array
|
||||
->html(),
|
||||
|
||||
ArrayColumn::make('Kategori')
|
||||
->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>'))
|
||||
->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>'))
|
||||
->flexRow(['class' => 'gap-2 flex-wrap']),
|
||||
|
||||
Column::make('Merek', 'brand.name')->searchable()->sortable(),
|
||||
|
||||
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(),
|
||||
|
||||
Column::make('Harga Beli', 'cost_price')
|
||||
->label(function ($row) {
|
||||
$canSeePrice = PriceRequest::where('user_id', auth()->id())
|
||||
->where('model', Perfume::class)
|
||||
->whereNotNull('approved_at')
|
||||
->where('expires_at', '>', now())
|
||||
->exists();
|
||||
|
||||
->label(function ($row) use ($canSeePrice) {
|
||||
if ($canSeePrice) {
|
||||
return currency($row->cost_price, 'Rp');
|
||||
}
|
||||
@ -79,15 +79,15 @@ public function columns(): array
|
||||
->hideIf(! auth()->user()->hasRole(['Developer', 'Owner'])),
|
||||
|
||||
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()
|
||||
->sortable(),
|
||||
|
||||
ArrayColumn::make('Outlet')
|
||||
->data(
|
||||
fn ($value, $row) => $row->outlets
|
||||
->filter(fn ($outlet) => auth()->user()->outlets->pluck('id')->contains($outlet->id))
|
||||
->map(fn ($outlet) => [
|
||||
fn($value, $row) => $row->outlets
|
||||
->filter(fn($outlet) => auth()->user()->outlets->pluck('id')->contains($outlet->id))
|
||||
->map(fn($outlet) => [
|
||||
'id' => $outlet->hash,
|
||||
'perfume_id' => $row->hash,
|
||||
'name' => $outlet->name,
|
||||
@ -99,7 +99,7 @@ public function columns(): array
|
||||
$canAudit = auth()->user()->can('audit perfume');
|
||||
|
||||
$tag = $canAudit ? 'div' : 'a';
|
||||
$attrs = $canAudit ? '' : 'href="'.$route.'" wire:navigate';
|
||||
$attrs = $canAudit ? '' : 'href="' . $route . '" wire:navigate';
|
||||
|
||||
return Blade::render("
|
||||
<{$tag} {$attrs}
|
||||
@ -159,7 +159,7 @@ public function builder(): Builder
|
||||
])
|
||||
->whereHas(
|
||||
'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'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,10 +20,16 @@ class ProductsTable extends DataTableComponent
|
||||
|
||||
protected $model = Product::class;
|
||||
|
||||
protected ?int $refreshTime = 3000;
|
||||
protected int $refreshTime = 3000;
|
||||
|
||||
public function columns(): array
|
||||
{
|
||||
$canSeePrice = PriceRequest::where('user_id', auth()->id())
|
||||
->where('module', 'produk')
|
||||
->approved()
|
||||
->where('finalized_at', '>', now()->subMinute(10))
|
||||
->exists();
|
||||
|
||||
return [
|
||||
Column::make('Nama')
|
||||
->label(function ($row) {
|
||||
@ -41,13 +47,7 @@ public function columns(): array
|
||||
->html(),
|
||||
|
||||
Column::make('Harga Beli', 'cost_price')
|
||||
->label(function ($row) {
|
||||
$canSeePrice = PriceRequest::where('user_id', auth()->id())
|
||||
->where('model', Product::class)
|
||||
->whereNotNull('approved_at')
|
||||
->where('expires_at', '>', now())
|
||||
->exists();
|
||||
|
||||
->label(function ($row) use ($canSeePrice) {
|
||||
if ($canSeePrice) {
|
||||
return currency($row->cost_price, 'Rp');
|
||||
}
|
||||
@ -68,15 +68,15 @@ public function columns(): array
|
||||
->hideIf(! auth()->user()->hasRole(['Developer', 'Owner'])),
|
||||
|
||||
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()
|
||||
->sortable(),
|
||||
|
||||
ArrayColumn::make('Outlet')
|
||||
->data(
|
||||
fn ($value, $row) => $row->outlets
|
||||
->filter(fn ($outlet) => auth()->user()->outlets->pluck('id')->contains($outlet->id))
|
||||
->map(fn ($outlet) => [
|
||||
fn($value, $row) => $row->outlets
|
||||
->filter(fn($outlet) => auth()->user()->outlets->pluck('id')->contains($outlet->id))
|
||||
->map(fn($outlet) => [
|
||||
'id' => $outlet->hash,
|
||||
'product_id' => $row->hash,
|
||||
'name' => $outlet->name,
|
||||
@ -88,7 +88,7 @@ public function columns(): array
|
||||
$canAudit = auth()->user()->can('audit product');
|
||||
|
||||
$tag = $canAudit ? 'div' : 'a';
|
||||
$attrs = $canAudit ? '' : 'href="'.$route.'" wire:navigate';
|
||||
$attrs = $canAudit ? '' : 'href="' . $route . '" wire:navigate';
|
||||
|
||||
return Blade::render("
|
||||
<{$tag} {$attrs}
|
||||
@ -134,7 +134,7 @@ public function builder(): Builder
|
||||
->with('outlets')
|
||||
->whereHas(
|
||||
'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'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
106
app/Livewire/Datatable/Studio/Manage/PriceRequestsTable.php
Normal file
106
app/Livewire/Datatable/Studio/Manage/PriceRequestsTable.php
Normal 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']);
|
||||
}
|
||||
}
|
||||
77
app/Livewire/Studio/Manage/PriceRequest.php
Normal file
77
app/Livewire/Studio/Manage/PriceRequest.php
Normal 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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,9 @@
|
||||
|
||||
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\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
@ -13,6 +16,32 @@ class PriceRequest extends Model
|
||||
|
||||
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
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Enums\PriceRequestStatus;
|
||||
use App\Models\PriceRequest;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
@ -9,7 +11,9 @@ class ViewServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function boot(): void
|
||||
{
|
||||
View::composer('*', function ($view) {
|
||||
$priceRequestCount = PriceRequest::where('status', PriceRequestStatus::PENDING->value)->count();
|
||||
|
||||
View::composer('*', function ($view) use ($priceRequestCount) {
|
||||
$sidebar = [
|
||||
[
|
||||
'heading' => 'Dasbor',
|
||||
@ -165,6 +169,14 @@ public function boot(): void
|
||||
'match' => 'studio.manage.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',
|
||||
'icon' => 'question-mark-circle',
|
||||
|
||||
@ -2,102 +2,57 @@
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Models\Bottle;
|
||||
use App\Models\Perfume;
|
||||
use App\Jobs\StorePushNotification;
|
||||
use App\Models\PriceRequest;
|
||||
use App\Models\Product;
|
||||
use App\Models\PushNotification;
|
||||
use App\Models\User;
|
||||
use App\Models\Whatsapp;
|
||||
use Flux\Flux;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
trait WithShowPrice
|
||||
{
|
||||
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())
|
||||
->where('model', $model)
|
||||
->where('created_at', '>', now()->subMinutes(30))
|
||||
->where('module', $module)
|
||||
->pending()
|
||||
->exists();
|
||||
|
||||
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();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$whatsapp = Whatsapp::first();
|
||||
$owner = User::role('owner')->with('employee')->first();
|
||||
$owner = User::role(['owner', 'Developer'])->with('employee')->first();
|
||||
|
||||
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();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$phoneNumber = formatPhoneNumber($owner->employee?->phone_number, '62');
|
||||
$causerName = auth()->user()->employee?->full_name;
|
||||
|
||||
try {
|
||||
$priceRequest = PriceRequest::create([
|
||||
DB::transaction(function () use ($module, $owner, $causerName) {
|
||||
PriceRequest::create([
|
||||
'user_id' => auth()->id(),
|
||||
'model' => $model,
|
||||
'expires_at' => now()->addMinutes(30),
|
||||
'module' => $module,
|
||||
]);
|
||||
|
||||
$signedUrl = URL::temporarySignedRoute(
|
||||
'price-request.approve',
|
||||
$priceRequest->expires_at,
|
||||
['priceRequest' => $priceRequest->hash]
|
||||
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'),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
$priceRequest->update([
|
||||
'signature' => hash('sha256', $signedUrl),
|
||||
]);
|
||||
|
||||
$causerName = auth()->user()->employee?->full_name;
|
||||
|
||||
$response = Http::withHeaders([
|
||||
'id' => $whatsapp->zawa_id,
|
||||
'session-id' => $whatsapp->session_id,
|
||||
'Content-Type' => 'application/json',
|
||||
])->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()) {
|
||||
$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');
|
||||
}
|
||||
$this->toast('Permintaan persetujuan berhasil dikirim.');
|
||||
|
||||
Flux::modal('show-price')->close();
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\PriceRequestStatus;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@ -14,11 +15,9 @@ public function up(): void
|
||||
Schema::create('price_requests', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('model', 20);
|
||||
$table->string('signature')->unique()->nullable();
|
||||
$table->timestamp('last_requested_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamp('approved_at')->nullable();
|
||||
$table->string('module', 20);
|
||||
$table->enum('status', [PriceRequestStatus::values()])->default(PriceRequestStatus::PENDING)->comment(PriceRequestStatus::comment());
|
||||
$table->timestamp('finalized_at')->nullable();
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate();
|
||||
$table->softDeletes();
|
||||
|
||||
@ -118,6 +118,10 @@ public function run(): void
|
||||
'update article',
|
||||
'delete article',
|
||||
|
||||
'view price request',
|
||||
'approve price request',
|
||||
'reject price request',
|
||||
|
||||
'view faq',
|
||||
'create faq',
|
||||
'update faq',
|
||||
|
||||
@ -26,5 +26,5 @@ class="text-sm">
|
||||
</div>
|
||||
|
||||
@include('components.confirmation.delete')
|
||||
@include('components.confirmation.show-price', ['module' => 'bottle'])
|
||||
@include('components.confirmation.show-price', ['module' => 'botol'])
|
||||
</flux:main>
|
||||
|
||||
@ -26,5 +26,5 @@ class="text-sm">
|
||||
</div>
|
||||
|
||||
@include('components.confirmation.delete')
|
||||
@include('components.confirmation.show-price', ['module' => 'perfume'])
|
||||
@include('components.confirmation.show-price', ['module' => 'parfum'])
|
||||
</flux:main>
|
||||
|
||||
@ -26,5 +26,5 @@ class="text-sm">
|
||||
</div>
|
||||
|
||||
@include('components.confirmation.delete')
|
||||
@include('components.confirmation.show-price', ['module' => 'product'])
|
||||
@include('components.confirmation.show-price', ['module' => 'produk'])
|
||||
</flux:main>
|
||||
|
||||
@ -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>
|
||||
@ -33,6 +33,7 @@
|
||||
use App\Livewire\Studio\Manage\Order\Create as OrderCreate;
|
||||
use App\Livewire\Studio\Manage\Order\Index as OrderIndex;
|
||||
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\Index as PurchaseIndex;
|
||||
use App\Livewire\Studio\Master\Formula as FormulaComponent;
|
||||
@ -176,6 +177,12 @@
|
||||
Route::get('/', FaqComponent::class)->name('index')->middleware('can:view 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 () {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user