chore: menghapus fitur permintaan harga

This commit is contained in:
Yoga Pangestu 2025-12-27 22:16:39 +07:00
parent d239d58029
commit e9dfc8142a
21 changed files with 5 additions and 578 deletions

View File

@ -1,33 +0,0 @@
<?php
namespace App\Enums;
use App\Traits\Enums\WithCommentEnum;
use App\Traits\Enums\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(): string
{
return match ($this) {
self::PENDING => 'gray',
self::APPROVED => 'emerald',
self::REJECTED => 'red',
};
}
}

View File

@ -3,13 +3,11 @@
namespace App\Livewire\Datatable\Catalog;
use App\Models\Bottle;
use App\Models\PriceRequest;
use App\Traits\Datatable\WithConfiguration;
use App\Traits\Datatable\WithPrependColumn;
use App\Traits\Media\WithMediaHandler;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Str;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelLivewireTables\Views\Columns\ArrayColumn;
@ -25,12 +23,6 @@ class BottlesTable extends DataTableComponent
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) {
@ -47,27 +39,6 @@ public function columns(): array
})
->html(),
Column::make('Harga Beli', 'cost_price')
->label(function ($row) use ($canSeePrice) {
if ($canSeePrice) {
return formatCurrencyNumber($row->cost_price, 'Rp');
}
$masked = Str::mask(formatCurrencyNumber($row->cost_price, 'Rp'), '*', 2);
return Blade::render('
<flux:modal.trigger name="show-price">
<flux:text class="cursor-pointer text-gray-400 hover:text-gray-600">
{{ $masked }}
</flux:text>
</flux:modal.trigger>
', ['masked' => $masked]);
})
->searchable()
->sortable()
->html()
->hideIf(! auth()->user()->hasRole(['Developer', 'Owner'])),
Column::make('Harga Jual', 'sale_price')
->label(fn ($row, $column) => formatCurrencyNumber($row->sale_price, 'Rp'))
->searchable()

View File

@ -3,13 +3,11 @@
namespace App\Livewire\Datatable\Catalog;
use App\Models\Perfume;
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 Illuminate\Support\Str;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelLivewireTables\Views\Columns\ArrayColumn;
@ -24,12 +22,6 @@ class PerfumesTable extends DataTableComponent
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) {
@ -57,27 +49,6 @@ public function columns(): array
->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) use ($canSeePrice) {
if ($canSeePrice) {
return formatCurrencyNumber($row->cost_price, 'Rp');
}
$masked = Str::mask(formatCurrencyNumber($row->cost_price, 'Rp'), '*', 2);
return Blade::render('
<flux:modal.trigger name="show-price">
<flux:text class="cursor-pointer text-gray-400 hover:text-gray-600">
{{ $masked }}
</flux:text>
</flux:modal.trigger>
', ['masked' => $masked]);
})
->searchable()
->sortable()
->html()
->hideIf(! auth()->user()->hasRole(['Developer', 'Owner'])),
Column::make('Harga Jual', 'sale_price')
->label(fn ($row, $column) => formatCurrencyNumber($row->sale_price, 'Rp'))
->searchable()

View File

@ -2,14 +2,12 @@
namespace App\Livewire\Datatable\Catalog;
use App\Models\PriceRequest;
use App\Models\Product;
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 Illuminate\Support\Str;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelLivewireTables\Views\Columns\ArrayColumn;
@ -24,12 +22,6 @@ class ProductsTable extends DataTableComponent
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) {
@ -46,27 +38,6 @@ public function columns(): array
})
->html(),
Column::make('Harga Beli', 'cost_price')
->label(function ($row) use ($canSeePrice) {
if ($canSeePrice) {
return formatCurrencyNumber($row->cost_price, 'Rp');
}
$masked = Str::mask(formatCurrencyNumber($row->cost_price, 'Rp'), '*', 2);
return Blade::render('
<flux:modal.trigger name="show-price">
<flux:text class="cursor-pointer text-gray-400 hover:text-gray-600">
{{ $masked }}
</flux:text>
</flux:modal.trigger>
', ['masked' => $masked]);
})
->searchable()
->sortable()
->html()
->hideIf(! auth()->user()->hasRole(['Developer', 'Owner'])),
Column::make('Harga Jual', 'sale_price')
->label(fn ($row, $column) => formatCurrencyNumber($row->sale_price, 'Rp'))
->searchable()

View File

@ -1,106 +0,0 @@
<?php
namespace App\Livewire\Datatable\Information;
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 WithAppendColumn, WithConfiguration, WithPrependColumn;
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 = formatDateLocalized($row->created_at);
$startAgo = formatRelativeTime($row->created_at);
$endDate = formatDateLocalized($row->finalized_at);
$endAgo = formatRelativeTime($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.actions.table.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.actions.table.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

@ -7,7 +7,6 @@
use App\Traits\Components\WithConfirmation;
use App\Traits\Components\WithToast;
use App\Traits\Notification\WithSubscribeNotification;
use App\Traits\Pricing\WithShowPrice;
use App\Traits\Utilities\WithRequestNotification;
use Flux\Flux;
use Illuminate\Contracts\View\View;
@ -17,7 +16,7 @@
#[Title('Botol')]
class Index extends Component
{
use WithAuthorization, WithConfirmation, WithRequestNotification, WithShowPrice, WithSubscribeNotification, WithToast;
use WithAuthorization, WithConfirmation, WithRequestNotification, WithSubscribeNotification, WithToast;
public function delete(Bottle $bottle): void
{

View File

@ -7,7 +7,6 @@
use App\Traits\Components\WithConfirmation;
use App\Traits\Components\WithToast;
use App\Traits\Notification\WithSubscribeNotification;
use App\Traits\Pricing\WithShowPrice;
use App\Traits\Utilities\WithRequestNotification;
use Flux\Flux;
use Livewire\Attributes\Title;
@ -16,7 +15,7 @@
#[Title('Parfum')]
class Index extends Component
{
use WithAuthorization, WithConfirmation, WithRequestNotification, WithShowPrice, WithSubscribeNotification, WithToast;
use WithAuthorization, WithConfirmation, WithRequestNotification, WithSubscribeNotification, WithToast;
public function delete(Perfume $perfume)
{

View File

@ -7,7 +7,6 @@
use App\Traits\Components\WithConfirmation;
use App\Traits\Components\WithToast;
use App\Traits\Notification\WithSubscribeNotification;
use App\Traits\Pricing\WithShowPrice;
use App\Traits\Utilities\WithRequestNotification;
use Flux\Flux;
use Illuminate\Contracts\View\View;
@ -17,7 +16,7 @@
#[Title('Produk')]
class Index extends Component
{
use WithAuthorization, WithConfirmation, WithRequestNotification, WithShowPrice, WithSubscribeNotification, WithToast;
use WithAuthorization, WithConfirmation, WithRequestNotification, WithSubscribeNotification, WithToast;
public function delete(Product $product): void
{

View File

@ -1,77 +0,0 @@
<?php
namespace App\Livewire\Studio\Information;
use App\Enums\PriceRequestStatus;
use App\Jobs\StorePushNotification;
use App\Models\PriceRequest as PriceRequestModel;
use App\Models\PushNotification;
use App\Traits\Authorization\WithAuthorization;
use App\Traits\Components\WithConfirmation;
use App\Traits\Components\WithToast;
use App\Traits\Notification\WithSubscribeNotification;
use Flux\Flux;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Title('Permintaan Harga')]
class PriceRequest extends Component
{
use WithAuthorization, WithConfirmation, WithSubscribeNotification, WithToast;
public function approve(PriceRequestModel $priceRequest): void
{
$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): void
{
$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(): View
{
return view('livewire.studio.information.price-requests', [
'pageTitle' => 'Permintaan Harga',
]);
}
}

View File

@ -1,49 +0,0 @@
<?php
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;
use Veelasky\LaravelHashId\Eloquent\HashableId;
class PriceRequest extends Model
{
use HashableId, SoftDeletes;
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);
}
}

View File

@ -135,11 +135,6 @@ public function pointRecords(): HasMany
return $this->hasMany(PointRecord::class);
}
public function priceRequests(): HasMany
{
return $this->hasMany(PriceRequest::class);
}
public function purchaseItems(): HasMany
{
return $this->hasMany(PurchaseItem::class);

View File

@ -2,11 +2,8 @@
namespace App\Providers;
use App\Enums\PriceRequestStatus;
use App\Models\PriceRequest;
use App\Settings\ContactSettings;
use App\Settings\GeneralSettings;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
@ -14,20 +11,7 @@ class ViewServiceProvider extends ServiceProvider
{
public function boot(): void
{
if (! Schema::hasTable('price_requests')) {
return;
}
if (Schema::hasTable('price_requests') && Schema::hasColumn('price_requests', 'status')) {
$priceRequestCount = PriceRequest::where(
'status',
PriceRequestStatus::PENDING->value
)->count();
} else {
$priceRequestCount = 0;
}
View::composer('*', function ($view) use ($priceRequestCount) {
View::composer('*', function ($view) {
$sidebar = [
[
'heading' => 'Dasbor',
@ -230,14 +214,6 @@ public function boot(): void
'match' => 'studio.information.broadcast.*',
'can' => 'view broadcast',
],
[
'label' => 'Permintaan Harga',
'icon' => 'exclamation-triangle',
'route' => 'studio.information.price_request.index',
'match' => 'studio.information.price_request.*',
'can' => 'view price request',
'badge' => $priceRequestCount,
],
[
'label' => 'FAQs',
'icon' => 'question-mark-circle',

View File

@ -1,59 +0,0 @@
<?php
namespace App\Traits\Pricing;
use App\Jobs\StorePushNotification;
use App\Models\PriceRequest;
use App\Models\PushNotification;
use App\Models\User;
use Flux\Flux;
use Illuminate\Support\Facades\DB;
trait WithShowPrice
{
public function requestPrice(string $module): void
{
$recentRequest = PriceRequest::where('user_id', auth()->id())
->where('module', $module)
->pending()
->exists();
if ($recentRequest) {
$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;
}
$owner = User::role(['owner', 'Developer'])->with('employee')->first();
if (! $owner) {
$this->toast('Owner belum terdaftar, silakan hubungi Admin.', 'Gagal', 'danger');
Flux::modal('show-price')->close();
return;
}
$causerName = auth()->user()->employee?->full_name;
DB::transaction(function () use ($module, $owner, $causerName) {
PriceRequest::create([
'user_id' => auth()->id(),
'module' => $module,
]);
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.information.price_request.index'),
],
);
});
$this->toast('Permintaan persetujuan berhasil dikirim.');
Flux::modal('show-price')->close();
}
}

View File

@ -1,51 +0,0 @@
<?php
namespace Database\Factories;
use App\Enums\PriceRequestStatus;
use App\Models\PriceRequest;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class PriceRequestFactory extends Factory
{
protected $model = PriceRequest::class;
public function definition(): array
{
$status = $this->faker->randomElement(PriceRequestStatus::cases());
return [
'user_id' => User::factory(),
'module' => $this->faker->randomElement(['product', 'perfume', 'bottle']),
'status' => $status,
'finalized_at' => $status === PriceRequestStatus::PENDING
? null
: $this->faker->dateTime(),
];
}
public function pending(): Factory
{
return $this->state(fn () => [
'status' => PriceRequestStatus::PENDING,
'finalized_at' => null,
]);
}
public function approved(): Factory
{
return $this->state(fn () => [
'status' => PriceRequestStatus::APPROVED,
'finalized_at' => $this->faker->dateTime(),
]);
}
public function rejected(): Factory
{
return $this->state(fn () => [
'status' => PriceRequestStatus::REJECTED,
'finalized_at' => $this->faker->dateTime(),
]);
}
}

View File

@ -1,34 +0,0 @@
<?php
use App\Enums\PriceRequestStatus;
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('price_requests', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$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();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('price_requests');
}
};

View File

@ -128,10 +128,6 @@ public function run(): void
'update article',
'delete article',
'view price request',
'approve price request',
'reject price request',
'view faq',
'create faq',
'update faq',
@ -147,6 +143,7 @@ public function run(): void
'delete broadcast',
'view warehouse',
'show warehouse',
'create warehouse',
'update warehouse',
'delete warehouse',

View File

@ -1,19 +0,0 @@
<flux:modal name="show-price" class="w-[22rem]">
<div class="space-y-6">
<div>
<flux:heading size="lg">Konfirmasi</flux:heading>
<flux:text class="mt-2">Apakah Anda ingin mengajukan permintaan persetujuan kepada owner untuk
mengakses data harga beli?</flux:text>
</div>
<div class="flex gap-2">
<flux:spacer />
<flux:modal.close>
<flux:button variant="ghost">Batal</flux:button>
</flux:modal.close>
<flux:button type="submit" variant="primary"
wire:click="requestPrice('{{ $module }}')">
Ya, Ajukan
</flux:button>
</div>
</div>
</flux:modal>

View File

@ -33,5 +33,4 @@ class="text-sm">
'buttonColor' => 'danger',
'buttonText' => 'Ya, Hapus',
])
@include('components.modals.confirmations.request-price', ['module' => 'botol'])
</flux:main>

View File

@ -40,5 +40,4 @@ class="text-sm">
'buttonColor' => 'danger',
'buttonText' => 'Ya, Hapus',
])
@include('components.modals.confirmations.request-price', ['module' => 'parfum'])
</flux:main>

View File

@ -40,5 +40,4 @@ class="text-sm">
'buttonColor' => 'danger',
'buttonText' => 'Ya, Hapus',
])
@include('components.modals.confirmations.request-price', ['module' => 'produk'])
</flux:main>

View File

@ -1,20 +0,0 @@
<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.information.price-requests-table />
</div>
@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',
])
</flux:main>