refactor: streamline enum management and enhance role assignment logic in EmployeeController for improved maintainability
This commit is contained in:
parent
7508227768
commit
27c5b5fffa
@ -3,7 +3,6 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Enums\PayrollAdjustmentType;
|
||||
use App\Enums\PayrollPeriodStatus;
|
||||
use App\Models\Attendance;
|
||||
use App\Models\Employee;
|
||||
use App\Models\LeaveRequest;
|
||||
|
||||
@ -2,8 +2,12 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Traits\ProvidesEnumOptions;
|
||||
|
||||
enum ActivityEventLabel: string
|
||||
{
|
||||
use ProvidesEnumOptions;
|
||||
|
||||
case CREATED = 'created';
|
||||
case UPDATED = 'updated';
|
||||
case DELETED = 'deleted';
|
||||
@ -20,26 +24,4 @@ public function label(): string
|
||||
self::LOGOUT => 'Keluar',
|
||||
};
|
||||
}
|
||||
|
||||
public static function labelFor(?string $event): string
|
||||
{
|
||||
if ($event === null || $event === '') {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return self::tryFrom($event)?->label() ?? ucfirst($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{value: string, label: string}>
|
||||
*/
|
||||
public static function selectOptions(): array
|
||||
{
|
||||
return collect(self::cases())
|
||||
->map(fn (self $event) => [
|
||||
'value' => $event->value,
|
||||
'label' => $event->label(),
|
||||
])
|
||||
->all();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Traits\ProvidesEnumOptions;
|
||||
use InvalidArgumentException;
|
||||
|
||||
enum CuttingStatus: string
|
||||
{
|
||||
@ -25,62 +24,4 @@ public function label(): string
|
||||
self::REJECTED => 'Ditolak',
|
||||
};
|
||||
}
|
||||
|
||||
public function isEditable(): bool
|
||||
{
|
||||
return in_array($this, [self::IN_PROGRESS, self::REJECTED], true);
|
||||
}
|
||||
|
||||
public function canTransitionTo(self $status): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::IN_PROGRESS => $status === self::COMPLETED,
|
||||
self::COMPLETED => in_array($status, [self::PENDING_VERIFICATION, self::REJECTED, self::IN_PROGRESS], true),
|
||||
self::PENDING_VERIFICATION => in_array($status, [self::VERIFIED, self::REJECTED, self::COMPLETED], true),
|
||||
self::REJECTED => $status === self::IN_PROGRESS,
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
public function transitionPermission(): Permission
|
||||
{
|
||||
return match ($this) {
|
||||
self::COMPLETED => Permission::CUTTINGS_COMPLETE,
|
||||
self::PENDING_VERIFICATION => Permission::CUTTINGS_VERIFY,
|
||||
self::VERIFIED => Permission::CUTTINGS_VERIFY,
|
||||
self::REJECTED => Permission::CUTTINGS_REJECT,
|
||||
self::IN_PROGRESS => Permission::CUTTINGS_UPDATE,
|
||||
default => throw new InvalidArgumentException('Status tidak mendukung transisi.'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{status: string, label: string, destructive: bool, permission: string, icon_only: bool}>
|
||||
*/
|
||||
public function availableActions(): array
|
||||
{
|
||||
return match ($this) {
|
||||
self::IN_PROGRESS => [
|
||||
[
|
||||
'status' => self::COMPLETED->value,
|
||||
'label' => 'Selesai',
|
||||
'destructive' => false,
|
||||
'permission' => Permission::CUTTINGS_COMPLETE->value,
|
||||
'icon_only' => false,
|
||||
],
|
||||
],
|
||||
self::COMPLETED => [],
|
||||
self::PENDING_VERIFICATION => [],
|
||||
self::REJECTED => [
|
||||
[
|
||||
'status' => self::IN_PROGRESS->value,
|
||||
'label' => 'Kembalikan ke Proses',
|
||||
'destructive' => false,
|
||||
'permission' => Permission::CUTTINGS_UPDATE->value,
|
||||
'icon_only' => false,
|
||||
],
|
||||
],
|
||||
default => [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,13 +20,4 @@ public function label(): string
|
||||
self::TIKTOK => 'TikTok',
|
||||
};
|
||||
}
|
||||
|
||||
public function defaultPriceType(): ?PriceType
|
||||
{
|
||||
return match ($this) {
|
||||
self::STORE => null,
|
||||
self::SHOPEE => PriceType::SHOPEE,
|
||||
self::TIKTOK => PriceType::TIKTOK,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Traits\ProvidesEnumOptions;
|
||||
use InvalidArgumentException;
|
||||
|
||||
enum OrderStatus: string
|
||||
{
|
||||
@ -23,70 +22,4 @@ public function label(): string
|
||||
self::CANCELLED => 'Dibatalkan',
|
||||
};
|
||||
}
|
||||
|
||||
public function isEditable(): bool
|
||||
{
|
||||
return in_array($this, [self::PENDING, self::PROCESSING], true);
|
||||
}
|
||||
|
||||
public function canTransitionTo(self $status): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::PENDING => in_array($status, [self::PROCESSING, self::CANCELLED], true),
|
||||
self::PROCESSING => in_array($status, [self::COMPLETED, self::CANCELLED], true),
|
||||
self::COMPLETED, self::CANCELLED => false,
|
||||
};
|
||||
}
|
||||
|
||||
public function transitionPermission(): Permission
|
||||
{
|
||||
return match ($this) {
|
||||
self::PROCESSING => Permission::ORDERS_SEND,
|
||||
self::COMPLETED => Permission::ORDERS_COMPLETE,
|
||||
self::CANCELLED => Permission::ORDERS_CANCEL,
|
||||
default => throw new InvalidArgumentException('Status tidak mendukung transisi.'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{status: string, label: string, destructive: bool, permission: string, icon_only: bool}>
|
||||
*/
|
||||
public function availableActions(): array
|
||||
{
|
||||
return match ($this) {
|
||||
self::PENDING => [
|
||||
[
|
||||
'status' => self::PROCESSING->value,
|
||||
'label' => 'Kirim',
|
||||
'destructive' => false,
|
||||
'permission' => Permission::ORDERS_SEND->value,
|
||||
'icon_only' => true,
|
||||
],
|
||||
[
|
||||
'status' => self::CANCELLED->value,
|
||||
'label' => 'Batalkan',
|
||||
'destructive' => true,
|
||||
'permission' => Permission::ORDERS_CANCEL->value,
|
||||
'icon_only' => true,
|
||||
],
|
||||
],
|
||||
self::PROCESSING => [
|
||||
[
|
||||
'status' => self::COMPLETED->value,
|
||||
'label' => 'Selesai',
|
||||
'destructive' => false,
|
||||
'permission' => Permission::ORDERS_COMPLETE->value,
|
||||
'icon_only' => true,
|
||||
],
|
||||
[
|
||||
'status' => self::CANCELLED->value,
|
||||
'label' => 'Batalkan',
|
||||
'destructive' => true,
|
||||
'permission' => Permission::ORDERS_CANCEL->value,
|
||||
'icon_only' => true,
|
||||
],
|
||||
],
|
||||
default => [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,57 +222,4 @@ public function label(): string
|
||||
self::ROLES_DELETE => 'Hapus Role',
|
||||
};
|
||||
}
|
||||
|
||||
public function group(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::DASHBOARD_VIEW => 'Umum',
|
||||
self::EMPLOYEES_VIEW, self::EMPLOYEES_CREATE, self::EMPLOYEES_UPDATE,
|
||||
self::EMPLOYEES_DELETE, self::EMPLOYEES_RESET_PASSWORD, self::EMPLOYEES_TOGGLE_STATUS => 'Pegawai',
|
||||
self::ATTENDANCES_VIEW, self::ATTENDANCES_CREATE, self::ATTENDANCES_DELETE,
|
||||
self::ATTENDANCES_MANAGE => 'Presensi',
|
||||
self::LEAVE_REQUESTS_VIEW, self::LEAVE_REQUESTS_CREATE, self::LEAVE_REQUESTS_UPDATE,
|
||||
self::LEAVE_REQUESTS_DELETE, self::LEAVE_REQUESTS_VERIFY => 'Cuti',
|
||||
self::CATEGORIES_VIEW, self::CATEGORIES_CREATE, self::CATEGORIES_UPDATE,
|
||||
self::CATEGORIES_DELETE => 'Kategori',
|
||||
self::SUPPLIERS_VIEW, self::SUPPLIERS_CREATE, self::SUPPLIERS_UPDATE,
|
||||
self::SUPPLIERS_DELETE => 'Supplier',
|
||||
self::CUSTOMERS_VIEW, self::CUSTOMERS_CREATE, self::CUSTOMERS_UPDATE,
|
||||
self::CUSTOMERS_DELETE => 'Pelanggan',
|
||||
self::PRODUCTS_VIEW, self::PRODUCTS_CREATE, self::PRODUCTS_UPDATE,
|
||||
self::PRODUCTS_DELETE, self::PRODUCTS_TOGGLE_STATUS => 'Produk',
|
||||
self::RAW_MATERIALS_VIEW, self::RAW_MATERIALS_CREATE, self::RAW_MATERIALS_UPDATE,
|
||||
self::RAW_MATERIALS_DELETE, self::RAW_MATERIALS_TOGGLE_STATUS => 'Bahan Baku',
|
||||
self::PURCHASES_VIEW, self::PURCHASES_CREATE, self::PURCHASES_UPDATE,
|
||||
self::PURCHASES_DELETE => 'Belanja',
|
||||
self::ORDERS_VIEW, self::ORDERS_CREATE, self::ORDERS_UPDATE,
|
||||
self::ORDERS_DELETE, self::ORDERS_SEND, self::ORDERS_COMPLETE,
|
||||
self::ORDERS_CANCEL => 'Pesanan',
|
||||
self::CUTTINGS_VIEW, self::CUTTINGS_CREATE, self::CUTTINGS_UPDATE,
|
||||
self::CUTTINGS_DELETE, self::CUTTINGS_COMPLETE, self::CUTTINGS_VERIFY,
|
||||
self::CUTTINGS_REJECT => 'Cutting',
|
||||
self::OWNER_VERIFICATIONS_VIEW, self::OWNER_VERIFICATIONS_VERIFY,
|
||||
self::OWNER_VERIFICATIONS_REJECT => 'Verifikasi Owner',
|
||||
self::STOCKS_VIEW => 'Stok',
|
||||
self::CASH_VIEW, self::CASH_DEPOSIT, self::CASH_WITHDRAW, self::CASH_UPDATE, self::CASH_DELETE => 'Kas',
|
||||
self::EXPENSES_VIEW, self::EXPENSES_CREATE, self::EXPENSES_UPDATE,
|
||||
self::EXPENSES_DELETE => 'Pengeluaran',
|
||||
self::EMPLOYEE_ADVANCES_VIEW, self::EMPLOYEE_ADVANCES_CREATE, self::EMPLOYEE_ADVANCES_UPDATE,
|
||||
self::EMPLOYEE_ADVANCES_DELETE, self::EMPLOYEE_ADVANCES_VERIFY,
|
||||
self::EMPLOYEE_ADVANCES_PAY => 'Kasbon',
|
||||
self::PAYROLL_VIEW, self::PAYROLL_ADJUST => 'Gaji',
|
||||
self::SETTINGS_VIEW, self::SETTINGS_UPDATE => 'Pengaturan Aplikasi',
|
||||
self::ACTIVITY_LOGS_VIEW => 'Log Aktivitas',
|
||||
self::ROLES_VIEW, self::ROLES_CREATE, self::ROLES_UPDATE,
|
||||
self::ROLES_DELETE => 'Role & Permission',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function values(): array
|
||||
{
|
||||
return array_column(self::cases(), 'value');
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,12 +30,4 @@ public function label(): string
|
||||
self::HARGA_MODAL => 'Harga Modal',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function values(): array
|
||||
{
|
||||
return array_column(self::cases(), 'value');
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,12 +18,4 @@ public function label(): string
|
||||
self::REJECT => 'Reject',
|
||||
};
|
||||
}
|
||||
|
||||
public function stockColumn(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::GOOD => 'stock',
|
||||
self::REJECT => 'reject_stock',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,16 +12,6 @@ enum RawMaterialUnit: string
|
||||
case METER = 'meter';
|
||||
case KILOGRAM = 'kilogram';
|
||||
|
||||
private const MIN_STOCK_YARD = 20;
|
||||
|
||||
private const MIN_STOCK_METER = 10;
|
||||
|
||||
private const MIN_STOCK_KILOGRAM = 5;
|
||||
|
||||
private const CM_PER_YARD = 91.44;
|
||||
|
||||
private const CM_PER_METER = 100.0;
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return match ($this) {
|
||||
@ -39,71 +29,4 @@ public function abbreviation(): string
|
||||
self::KILOGRAM => 'kg',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function values(): array
|
||||
{
|
||||
return array_column(self::cases(), 'value');
|
||||
}
|
||||
|
||||
public function minStock(): float
|
||||
{
|
||||
return match ($this) {
|
||||
self::YARD => self::MIN_STOCK_YARD,
|
||||
self::METER => self::MIN_STOCK_METER,
|
||||
self::KILOGRAM => self::MIN_STOCK_KILOGRAM,
|
||||
};
|
||||
}
|
||||
|
||||
public function usesLengthUnit(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::YARD, self::METER => true,
|
||||
self::KILOGRAM => false,
|
||||
};
|
||||
}
|
||||
|
||||
public function cmPerUnit(): ?float
|
||||
{
|
||||
return match ($this) {
|
||||
self::YARD => self::CM_PER_YARD,
|
||||
self::METER => self::CM_PER_METER,
|
||||
self::KILOGRAM => null,
|
||||
};
|
||||
}
|
||||
|
||||
public function toCm(float $value): ?float
|
||||
{
|
||||
$cmPerUnit = $this->cmPerUnit();
|
||||
|
||||
if ($cmPerUnit === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $value * $cmPerUnit;
|
||||
}
|
||||
|
||||
public function fromCm(float $cm): float
|
||||
{
|
||||
$cmPerUnit = $this->cmPerUnit();
|
||||
|
||||
if ($cmPerUnit === null || $cmPerUnit <= 0) {
|
||||
return $cm;
|
||||
}
|
||||
|
||||
return $cm / $cmPerUnit;
|
||||
}
|
||||
|
||||
public function pricePerCm(int $pricePerUnit): ?float
|
||||
{
|
||||
$cmPerUnit = $this->cmPerUnit();
|
||||
|
||||
if ($cmPerUnit === null || $cmPerUnit <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $pricePerUnit / $cmPerUnit;
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,302 +30,4 @@ public function label(): string
|
||||
self::NON_OPERATOR => 'Non Operator',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<Permission>
|
||||
*/
|
||||
public function permissions(): array
|
||||
{
|
||||
return match ($this) {
|
||||
self::DEVELOPER, self::OWNER => array_values(array_filter(
|
||||
Permission::cases(),
|
||||
fn (Permission $permission) => ! in_array($permission, [
|
||||
Permission::ATTENDANCES_CREATE,
|
||||
Permission::ATTENDANCES_DELETE,
|
||||
], true)
|
||||
)),
|
||||
|
||||
self::DIREKTUR => [
|
||||
Permission::DASHBOARD_VIEW,
|
||||
|
||||
Permission::EMPLOYEES_VIEW,
|
||||
|
||||
Permission::STOCKS_VIEW,
|
||||
|
||||
Permission::ATTENDANCES_VIEW,
|
||||
Permission::ATTENDANCES_CREATE,
|
||||
Permission::ATTENDANCES_DELETE,
|
||||
Permission::ATTENDANCES_MANAGE,
|
||||
|
||||
Permission::LEAVE_REQUESTS_VIEW,
|
||||
Permission::LEAVE_REQUESTS_CREATE,
|
||||
Permission::LEAVE_REQUESTS_UPDATE,
|
||||
Permission::LEAVE_REQUESTS_DELETE,
|
||||
|
||||
Permission::CATEGORIES_VIEW,
|
||||
|
||||
Permission::SUPPLIERS_VIEW,
|
||||
|
||||
Permission::CUSTOMERS_VIEW,
|
||||
|
||||
Permission::PRODUCTS_VIEW,
|
||||
|
||||
Permission::RAW_MATERIALS_VIEW,
|
||||
|
||||
Permission::PURCHASES_VIEW,
|
||||
|
||||
Permission::ORDERS_VIEW,
|
||||
|
||||
Permission::CUTTINGS_VIEW,
|
||||
|
||||
Permission::CASH_VIEW,
|
||||
|
||||
Permission::EXPENSES_VIEW,
|
||||
|
||||
Permission::EMPLOYEE_ADVANCES_VIEW,
|
||||
Permission::EMPLOYEE_ADVANCES_CREATE,
|
||||
Permission::EMPLOYEE_ADVANCES_UPDATE,
|
||||
Permission::EMPLOYEE_ADVANCES_DELETE,
|
||||
Permission::EMPLOYEE_ADVANCES_PAY,
|
||||
|
||||
Permission::PAYROLL_VIEW,
|
||||
],
|
||||
|
||||
self::ADMIN_TOKO => [
|
||||
Permission::DASHBOARD_VIEW,
|
||||
|
||||
Permission::STOCKS_VIEW,
|
||||
|
||||
Permission::CUTTINGS_VERIFY,
|
||||
|
||||
Permission::ATTENDANCES_VIEW,
|
||||
Permission::ATTENDANCES_CREATE,
|
||||
Permission::ATTENDANCES_DELETE,
|
||||
|
||||
Permission::LEAVE_REQUESTS_VIEW,
|
||||
Permission::LEAVE_REQUESTS_CREATE,
|
||||
Permission::LEAVE_REQUESTS_UPDATE,
|
||||
Permission::LEAVE_REQUESTS_DELETE,
|
||||
|
||||
Permission::CATEGORIES_VIEW,
|
||||
Permission::CATEGORIES_CREATE,
|
||||
Permission::CATEGORIES_UPDATE,
|
||||
Permission::CATEGORIES_DELETE,
|
||||
|
||||
Permission::SUPPLIERS_VIEW,
|
||||
Permission::SUPPLIERS_CREATE,
|
||||
Permission::SUPPLIERS_UPDATE,
|
||||
Permission::SUPPLIERS_DELETE,
|
||||
|
||||
Permission::CUSTOMERS_VIEW,
|
||||
Permission::CUSTOMERS_CREATE,
|
||||
Permission::CUSTOMERS_UPDATE,
|
||||
Permission::CUSTOMERS_DELETE,
|
||||
|
||||
Permission::PRODUCTS_VIEW,
|
||||
Permission::PRODUCTS_CREATE,
|
||||
Permission::PRODUCTS_UPDATE,
|
||||
Permission::PRODUCTS_DELETE,
|
||||
Permission::PRODUCTS_TOGGLE_STATUS,
|
||||
|
||||
Permission::ORDERS_VIEW,
|
||||
Permission::ORDERS_CREATE,
|
||||
Permission::ORDERS_UPDATE,
|
||||
Permission::ORDERS_DELETE,
|
||||
Permission::ORDERS_SEND,
|
||||
Permission::ORDERS_COMPLETE,
|
||||
Permission::ORDERS_CANCEL,
|
||||
|
||||
Permission::CASH_VIEW,
|
||||
Permission::CASH_DEPOSIT,
|
||||
Permission::CASH_WITHDRAW,
|
||||
Permission::CASH_UPDATE,
|
||||
Permission::CASH_DELETE,
|
||||
|
||||
Permission::EXPENSES_VIEW,
|
||||
Permission::EXPENSES_CREATE,
|
||||
Permission::EXPENSES_UPDATE,
|
||||
Permission::EXPENSES_DELETE,
|
||||
|
||||
Permission::EMPLOYEE_ADVANCES_VIEW,
|
||||
Permission::EMPLOYEE_ADVANCES_CREATE,
|
||||
Permission::EMPLOYEE_ADVANCES_UPDATE,
|
||||
Permission::EMPLOYEE_ADVANCES_DELETE,
|
||||
Permission::EMPLOYEE_ADVANCES_PAY,
|
||||
|
||||
Permission::PAYROLL_VIEW,
|
||||
Permission::PAYROLL_ADJUST,
|
||||
],
|
||||
|
||||
self::CASHIER => [
|
||||
Permission::DASHBOARD_VIEW,
|
||||
|
||||
Permission::ATTENDANCES_VIEW,
|
||||
Permission::ATTENDANCES_CREATE,
|
||||
Permission::ATTENDANCES_DELETE,
|
||||
|
||||
Permission::LEAVE_REQUESTS_VIEW,
|
||||
Permission::LEAVE_REQUESTS_CREATE,
|
||||
Permission::LEAVE_REQUESTS_UPDATE,
|
||||
Permission::LEAVE_REQUESTS_DELETE,
|
||||
|
||||
Permission::CUSTOMERS_VIEW,
|
||||
Permission::CUSTOMERS_CREATE,
|
||||
Permission::CUSTOMERS_UPDATE,
|
||||
Permission::CUSTOMERS_DELETE,
|
||||
|
||||
Permission::PRODUCTS_VIEW,
|
||||
|
||||
Permission::ORDERS_VIEW,
|
||||
Permission::ORDERS_CREATE,
|
||||
Permission::ORDERS_UPDATE,
|
||||
Permission::ORDERS_DELETE,
|
||||
Permission::ORDERS_SEND,
|
||||
Permission::ORDERS_COMPLETE,
|
||||
Permission::ORDERS_CANCEL,
|
||||
|
||||
Permission::EMPLOYEE_ADVANCES_VIEW,
|
||||
Permission::EMPLOYEE_ADVANCES_CREATE,
|
||||
Permission::EMPLOYEE_ADVANCES_UPDATE,
|
||||
Permission::EMPLOYEE_ADVANCES_DELETE,
|
||||
Permission::EMPLOYEE_ADVANCES_PAY,
|
||||
|
||||
Permission::PAYROLL_VIEW,
|
||||
Permission::PAYROLL_ADJUST,
|
||||
],
|
||||
|
||||
self::ADMIN_BAHAN_BAKU => [
|
||||
Permission::DASHBOARD_VIEW,
|
||||
|
||||
Permission::ATTENDANCES_VIEW,
|
||||
Permission::ATTENDANCES_CREATE,
|
||||
|
||||
Permission::LEAVE_REQUESTS_VIEW,
|
||||
Permission::LEAVE_REQUESTS_CREATE,
|
||||
Permission::LEAVE_REQUESTS_UPDATE,
|
||||
Permission::LEAVE_REQUESTS_DELETE,
|
||||
|
||||
Permission::SUPPLIERS_VIEW,
|
||||
Permission::SUPPLIERS_CREATE,
|
||||
Permission::SUPPLIERS_UPDATE,
|
||||
Permission::SUPPLIERS_DELETE,
|
||||
|
||||
Permission::RAW_MATERIALS_VIEW,
|
||||
Permission::RAW_MATERIALS_CREATE,
|
||||
Permission::RAW_MATERIALS_UPDATE,
|
||||
Permission::RAW_MATERIALS_DELETE,
|
||||
Permission::RAW_MATERIALS_TOGGLE_STATUS,
|
||||
|
||||
Permission::PRODUCTS_VIEW,
|
||||
|
||||
Permission::CUTTINGS_VIEW,
|
||||
Permission::CUTTINGS_CREATE,
|
||||
Permission::CUTTINGS_UPDATE,
|
||||
Permission::CUTTINGS_DELETE,
|
||||
Permission::CUTTINGS_COMPLETE,
|
||||
Permission::CUTTINGS_VERIFY,
|
||||
|
||||
Permission::PURCHASES_VIEW,
|
||||
Permission::PURCHASES_CREATE,
|
||||
Permission::PURCHASES_UPDATE,
|
||||
Permission::PURCHASES_DELETE,
|
||||
|
||||
Permission::EMPLOYEE_ADVANCES_VIEW,
|
||||
Permission::EMPLOYEE_ADVANCES_CREATE,
|
||||
Permission::EMPLOYEE_ADVANCES_UPDATE,
|
||||
Permission::EMPLOYEE_ADVANCES_DELETE,
|
||||
Permission::EMPLOYEE_ADVANCES_PAY,
|
||||
|
||||
Permission::PAYROLL_VIEW,
|
||||
],
|
||||
|
||||
self::MARKETING => [
|
||||
Permission::DASHBOARD_VIEW,
|
||||
|
||||
Permission::ATTENDANCES_VIEW,
|
||||
Permission::ATTENDANCES_CREATE,
|
||||
|
||||
Permission::LEAVE_REQUESTS_VIEW,
|
||||
Permission::LEAVE_REQUESTS_CREATE,
|
||||
Permission::LEAVE_REQUESTS_UPDATE,
|
||||
Permission::LEAVE_REQUESTS_DELETE,
|
||||
|
||||
Permission::CUSTOMERS_VIEW,
|
||||
|
||||
Permission::PRODUCTS_VIEW,
|
||||
|
||||
Permission::ORDERS_VIEW,
|
||||
|
||||
Permission::EMPLOYEE_ADVANCES_VIEW,
|
||||
Permission::EMPLOYEE_ADVANCES_CREATE,
|
||||
Permission::EMPLOYEE_ADVANCES_UPDATE,
|
||||
Permission::EMPLOYEE_ADVANCES_DELETE,
|
||||
Permission::EMPLOYEE_ADVANCES_PAY,
|
||||
|
||||
Permission::PAYROLL_VIEW,
|
||||
],
|
||||
|
||||
self::NON_OPERATOR => [
|
||||
Permission::DASHBOARD_VIEW,
|
||||
|
||||
Permission::DASHBOARD_VIEW,
|
||||
|
||||
Permission::ATTENDANCES_VIEW,
|
||||
Permission::ATTENDANCES_CREATE,
|
||||
|
||||
Permission::LEAVE_REQUESTS_VIEW,
|
||||
Permission::LEAVE_REQUESTS_CREATE,
|
||||
Permission::LEAVE_REQUESTS_UPDATE,
|
||||
Permission::LEAVE_REQUESTS_DELETE,
|
||||
|
||||
Permission::EMPLOYEE_ADVANCES_VIEW,
|
||||
Permission::EMPLOYEE_ADVANCES_CREATE,
|
||||
Permission::EMPLOYEE_ADVANCES_UPDATE,
|
||||
Permission::EMPLOYEE_ADVANCES_DELETE,
|
||||
Permission::EMPLOYEE_ADVANCES_PAY,
|
||||
|
||||
Permission::PAYROLL_VIEW,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
public function isAssignable(): bool
|
||||
{
|
||||
return $this !== self::DEVELOPER;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{value: string, label: string}>
|
||||
*/
|
||||
public static function assignableSelectOptions(): array
|
||||
{
|
||||
return collect(self::cases())
|
||||
->filter(fn (self $role) => $role->isAssignable())
|
||||
->map(fn (self $role) => [
|
||||
'value' => $role->value,
|
||||
'label' => $role->label(),
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function assignableValues(): array
|
||||
{
|
||||
return collect(self::cases())
|
||||
->filter(fn (self $role) => $role->isAssignable())
|
||||
->map(fn (self $role) => $role->value)
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function values(): array
|
||||
{
|
||||
return array_column(self::cases(), 'value');
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,20 @@ public function __construct(
|
||||
private readonly EmployeeService $employeeService,
|
||||
) {}
|
||||
|
||||
private function assignableRoleOptions(): array
|
||||
{
|
||||
$assignable = config('roles.assignable', []);
|
||||
|
||||
return collect(Role::cases())
|
||||
->filter(fn (Role $role) => in_array($role->value, $assignable, true))
|
||||
->map(fn (Role $role) => [
|
||||
'value' => $role->value,
|
||||
'label' => $role->label(),
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$tableQuery = $this->parseDataTableQuery($request);
|
||||
@ -47,7 +61,7 @@ public function index(Request $request): Response
|
||||
'employment_status' => $employmentStatus,
|
||||
'is_active' => $isActive,
|
||||
]),
|
||||
'roles' => Role::assignableSelectOptions(),
|
||||
'roles' => $this->assignableRoleOptions(),
|
||||
'genders' => Gender::selectOptions(),
|
||||
'employmentStatuses' => EmploymentStatus::selectOptions(),
|
||||
]);
|
||||
@ -58,7 +72,7 @@ public function create(): Response
|
||||
return Inertia::render('admin/hr/employees/Create', [
|
||||
'genders' => Gender::selectOptions(),
|
||||
'employmentStatuses' => EmploymentStatus::selectOptions(),
|
||||
'roles' => Role::assignableSelectOptions(),
|
||||
'roles' => $this->assignableRoleOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -78,7 +92,7 @@ public function edit(User $user): Response
|
||||
return Inertia::render('admin/hr/employees/Edit', [
|
||||
'genders' => Gender::selectOptions(),
|
||||
'employmentStatuses' => EmploymentStatus::selectOptions(),
|
||||
'roles' => Role::assignableSelectOptions(),
|
||||
'roles' => $this->assignableRoleOptions(),
|
||||
'employee' => $user,
|
||||
]);
|
||||
}
|
||||
@ -94,20 +108,20 @@ public function update(EmployeeRequest $request, User $user): RedirectResponse
|
||||
|
||||
public function toggleStatus(ToggleStatusRequest $request, User $user): RedirectResponse
|
||||
{
|
||||
$this->employeeService->toggleStatus($user, $request->validated());
|
||||
$this->employeeService->toggleStatus($user);
|
||||
|
||||
$this->flashStatusUpdated('pegawai');
|
||||
$this->flashSuccess('Status pegawai berhasil diubah.');
|
||||
|
||||
return back();
|
||||
return redirect()->route('admin.hr.employees.index');
|
||||
}
|
||||
|
||||
public function resetPassword(User $user): RedirectResponse
|
||||
{
|
||||
$this->employeeService->resetPassword($user);
|
||||
|
||||
$this->flashSuccess('Kata sandi berhasil direset. Pengguna telah logout dari semua sesi.');
|
||||
$this->flashSuccess('Kata sandi pegawai berhasil direset.');
|
||||
|
||||
return back();
|
||||
return redirect()->route('admin.hr.employees.index');
|
||||
}
|
||||
|
||||
public function destroy(User $user): RedirectResponse
|
||||
|
||||
@ -57,7 +57,7 @@ public function store(CuttingRequest $request): RedirectResponse
|
||||
|
||||
public function edit(Cutting $cutting): Response|RedirectResponse
|
||||
{
|
||||
if (! $cutting->status->isEditable()) {
|
||||
if (! $this->cuttingService->isEditable($cutting->status)) {
|
||||
$this->flashError('Proses cutting tidak dapat diubah.');
|
||||
|
||||
return redirect()->route('admin.manage.cuttings.index');
|
||||
|
||||
@ -75,7 +75,7 @@ public function store(OrderRequest $request): RedirectResponse
|
||||
|
||||
public function edit(Order $order): Response|RedirectResponse
|
||||
{
|
||||
if (! $order->status->isEditable()) {
|
||||
if (! $this->orderService->isEditable($order->status)) {
|
||||
$this->flashError('Pesanan tidak dapat diubah.');
|
||||
|
||||
return redirect()->route('admin.manage.orders.index');
|
||||
|
||||
@ -35,11 +35,13 @@ public function index(Request $request): Response
|
||||
|
||||
public function create(): Response
|
||||
{
|
||||
$permissionGroups = config('permissions.groups');
|
||||
|
||||
$permissions = collect(PermissionEnum::cases())
|
||||
->map(fn (PermissionEnum $permission) => [
|
||||
'value' => $permission->value,
|
||||
'label' => $permission->label(),
|
||||
'group' => $permission->group(),
|
||||
'group' => $permissionGroups[$permission->value] ?? '-',
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
@ -62,11 +64,13 @@ public function edit(Role $role): Response
|
||||
{
|
||||
$role->load('permissions');
|
||||
|
||||
$permissionGroups = config('permissions.groups');
|
||||
|
||||
$permissions = collect(PermissionEnum::cases())
|
||||
->map(fn (PermissionEnum $permission) => [
|
||||
'value' => $permission->value,
|
||||
'label' => $permission->label(),
|
||||
'group' => $permission->group(),
|
||||
'group' => $permissionGroups[$permission->value] ?? '-',
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
@ -78,10 +82,10 @@ public function edit(Role $role): Response
|
||||
'role' => [
|
||||
'id' => $role->id,
|
||||
'name' => $role->name,
|
||||
'permission_names' => $role->permissions->pluck('name')->all(),
|
||||
'permissions' => $role->permissions->pluck('name'),
|
||||
'is_protected' => $isProtected,
|
||||
],
|
||||
'permissions' => $permissions,
|
||||
'isProtected' => $isProtected,
|
||||
]);
|
||||
}
|
||||
|
||||
@ -96,12 +100,9 @@ public function update(RoleRequest $request, Role $role): RedirectResponse
|
||||
|
||||
public function destroy(Role $role): RedirectResponse
|
||||
{
|
||||
try {
|
||||
$this->roleService->delete($role);
|
||||
$this->flashDeleted('Role');
|
||||
} catch (\InvalidArgumentException $exception) {
|
||||
$this->flashError($exception->getMessage());
|
||||
}
|
||||
$this->roleService->delete($role);
|
||||
|
||||
$this->flashDeleted('Role');
|
||||
|
||||
return redirect()->route('admin.system.roles.index');
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ public function rules(): array
|
||||
'gender' => ['nullable', Rule::enum(Gender::class)],
|
||||
'birth_date' => ['nullable', 'date', 'before:today'],
|
||||
'address' => ['nullable', 'string'],
|
||||
'role' => ['required', Rule::in(Role::assignableValues())],
|
||||
'role' => ['required', Rule::in(config('roles.assignable', []))],
|
||||
];
|
||||
|
||||
if (! $isOwner) {
|
||||
@ -61,10 +61,10 @@ public function attributes(): array
|
||||
'gender' => 'jenis kelamin',
|
||||
'birth_date' => 'tanggal lahir',
|
||||
'address' => 'alamat',
|
||||
'role' => 'role',
|
||||
'join_date' => 'tanggal bergabung',
|
||||
'employment_status' => 'status kepegawaian',
|
||||
'base_salary' => 'gaji pokok',
|
||||
'role' => 'role',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,7 +26,10 @@ public function authorize(): bool
|
||||
|| false;
|
||||
}
|
||||
|
||||
return $this->user()?->can($status->transitionPermission()->value) ?? false;
|
||||
$permissions = config('cutting-status.permissions', $status);
|
||||
$permission = $permissions[$status->value] ?? null;
|
||||
|
||||
return $permission ? ($this->user()?->can($permission->value) ?? false) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,7 +76,10 @@ public function withValidator(Validator $validator): void
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $cutting->status->canTransitionTo($status)) {
|
||||
$transitions = config('cutting-status.transitions', []);
|
||||
$allowedTargets = $transitions[$cutting->status->value] ?? [];
|
||||
|
||||
if (! in_array($status, $allowedTargets, true)) {
|
||||
$validator->errors()->add('status', 'Status cutting tidak dapat diubah.');
|
||||
}
|
||||
|
||||
|
||||
@ -90,7 +90,7 @@ public function withValidator(Validator $validator): void
|
||||
/** @var Order $order */
|
||||
$order = $this->route('order');
|
||||
|
||||
if (! $order->status->isEditable()) {
|
||||
if (! in_array($order->status, config('order-status.editable'), true)) {
|
||||
$validator->errors()->add('status', 'Pesanan tidak dapat diubah.');
|
||||
}
|
||||
});
|
||||
|
||||
@ -18,7 +18,10 @@ public function authorize(): bool
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->user()?->can($status->transitionPermission()->value) ?? false;
|
||||
$permissions = config('order-status.permissions', []);
|
||||
$permission = $permissions[$status->value] ?? null;
|
||||
|
||||
return $permission ? ($this->user()?->can($permission->value) ?? false) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,7 +55,10 @@ public function withValidator(Validator $validator): void
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $order->status->canTransitionTo($status)) {
|
||||
$transitions = config('order-status.transitions', []);
|
||||
$allowedTargets = $transitions[$order->status->value] ?? [];
|
||||
|
||||
if (! in_array($status, $allowedTargets, true)) {
|
||||
$validator->errors()->add('status', 'Status pesanan tidak dapat diubah.');
|
||||
}
|
||||
});
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace App\Services\Manage;
|
||||
|
||||
use App\Enums\CuttingStatus;
|
||||
use App\Enums\Permission;
|
||||
use App\Models\Cutting;
|
||||
use App\Models\CuttingMaterial;
|
||||
use App\Models\CuttingResult;
|
||||
@ -27,6 +28,34 @@ public function __construct(
|
||||
private readonly PushNotificationService $pushNotificationService,
|
||||
) {}
|
||||
|
||||
public function isEditable(CuttingStatus $status): bool
|
||||
{
|
||||
return in_array($status, config('cutting-status.editable'), true);
|
||||
}
|
||||
|
||||
public function canTransitionTo(CuttingStatus $from, CuttingStatus $to): bool
|
||||
{
|
||||
$transitions = config('cutting-status.transitions', []);
|
||||
|
||||
return in_array($to, $transitions[$from->value] ?? [], true);
|
||||
}
|
||||
|
||||
public function transitionPermission(CuttingStatus $status): Permission
|
||||
{
|
||||
$permissions = config('cutting-status.permissions', []);
|
||||
|
||||
return $permissions[$status->value]
|
||||
?? throw new \InvalidArgumentException('Status tidak mendukung transisi.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{status: string, label: string, destructive: bool, permission: string, icon_only: bool}>
|
||||
*/
|
||||
public function availableActions(CuttingStatus $status): array
|
||||
{
|
||||
return config('cutting-status.actions.'.$status->value, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
|
||||
*/
|
||||
@ -61,13 +90,13 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi
|
||||
->paginate(10)
|
||||
->withQueryString()
|
||||
->through(function (Cutting $cutting) use ($user) {
|
||||
$actions = collect($cutting->status->availableActions())
|
||||
$actions = collect($this->availableActions($cutting->status))
|
||||
->filter(fn (array $action) => $user->can($action['permission']))
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$cutting->setAttribute('available_actions', $actions);
|
||||
$cutting->setAttribute('is_editable', $cutting->status->isEditable());
|
||||
$cutting->setAttribute('is_editable', $this->isEditable($cutting->status));
|
||||
$this->appendCostPreview($cutting);
|
||||
|
||||
return $cutting;
|
||||
@ -91,13 +120,13 @@ public function getInProgressCuttings(User $user): Collection
|
||||
->latest()
|
||||
->get()
|
||||
->each(function (Cutting $cutting) use ($user): void {
|
||||
$actions = collect($cutting->status->availableActions())
|
||||
$actions = collect($this->availableActions($cutting->status))
|
||||
->filter(fn (array $action) => $user->can($action['permission']))
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$cutting->setAttribute('available_actions', $actions);
|
||||
$cutting->setAttribute('is_editable', $cutting->status->isEditable());
|
||||
$cutting->setAttribute('is_editable', $this->isEditable($cutting->status));
|
||||
$this->appendCostPreview($cutting);
|
||||
});
|
||||
}
|
||||
@ -119,13 +148,13 @@ public function getCompletedCuttings(User $user): Collection
|
||||
->latest()
|
||||
->get()
|
||||
->each(function (Cutting $cutting) use ($user): void {
|
||||
$actions = collect($cutting->status->availableActions())
|
||||
$actions = collect($this->availableActions($cutting->status))
|
||||
->filter(fn (array $action) => $user->can($action['permission']))
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$cutting->setAttribute('available_actions', $actions);
|
||||
$cutting->setAttribute('is_editable', $cutting->status->isEditable());
|
||||
$cutting->setAttribute('is_editable', $this->isEditable($cutting->status));
|
||||
$this->appendCostPreview($cutting);
|
||||
});
|
||||
}
|
||||
@ -463,7 +492,7 @@ public function create(array $validated, User $user): Cutting
|
||||
*/
|
||||
public function update(Cutting $cutting, array $validated): void
|
||||
{
|
||||
if (! $cutting->status->isEditable()) {
|
||||
if (! $this->isEditable($cutting->status)) {
|
||||
throw ValidationException::withMessages([
|
||||
'status' => 'Proses cutting tidak dapat diubah.',
|
||||
]);
|
||||
@ -557,7 +586,7 @@ public function transitionStatus(
|
||||
?array $results = null,
|
||||
?array $resultPrices = null,
|
||||
): void {
|
||||
if (! $cutting->status->canTransitionTo($status)) {
|
||||
if (! $this->canTransitionTo($cutting->status, $status)) {
|
||||
throw ValidationException::withMessages([
|
||||
'status' => 'Status proses cutting tidak dapat diubah.',
|
||||
]);
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
use App\Enums\OrderChannel;
|
||||
use App\Enums\OrderStatus;
|
||||
use App\Enums\PaymentType;
|
||||
use App\Enums\Permission;
|
||||
use App\Enums\PriceType;
|
||||
use App\Enums\ProductStockQuality;
|
||||
use App\Models\Customer;
|
||||
@ -33,6 +34,51 @@ public function __construct(
|
||||
private readonly CuttingResultPriceResolver $cuttingResultPriceResolver,
|
||||
) {}
|
||||
|
||||
public function isEditable(OrderStatus $status): bool
|
||||
{
|
||||
return in_array($status, config('order-status.editable'), true);
|
||||
}
|
||||
|
||||
public function canTransitionTo(OrderStatus $from, OrderStatus $to): bool
|
||||
{
|
||||
$transitions = config('order-status.transitions', []);
|
||||
|
||||
return in_array($to, $transitions[$from->value] ?? [], true);
|
||||
}
|
||||
|
||||
public function transitionPermission(OrderStatus $status): Permission
|
||||
{
|
||||
$permissions = config('order-status.permissions', []);
|
||||
|
||||
return $permissions[$status->value]
|
||||
?? throw new \InvalidArgumentException('Status tidak mendukung transisi.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{status: string, label: string, destructive: bool, permission: string, icon_only: bool}>
|
||||
*/
|
||||
public function availableActions(OrderStatus $status): array
|
||||
{
|
||||
return config('order-status.actions.'.$status->value, []);
|
||||
}
|
||||
|
||||
public function defaultPriceType(OrderChannel $channel): ?PriceType
|
||||
{
|
||||
return match ($channel) {
|
||||
OrderChannel::STORE => null,
|
||||
OrderChannel::SHOPEE => PriceType::SHOPEE,
|
||||
OrderChannel::TIKTOK => PriceType::TIKTOK,
|
||||
};
|
||||
}
|
||||
|
||||
public function stockColumn(ProductStockQuality $quality): string
|
||||
{
|
||||
return match ($quality) {
|
||||
ProductStockQuality::GOOD => 'stock',
|
||||
ProductStockQuality::REJECT => 'reject_stock',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
|
||||
*/
|
||||
@ -67,13 +113,13 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi
|
||||
->paginate(10)
|
||||
->withQueryString()
|
||||
->through(function (Order $order) use ($user) {
|
||||
$actions = collect($order->status->availableActions())
|
||||
$actions = collect($this->availableActions($order->status))
|
||||
->filter(fn (array $action) => $user->can($action['permission']))
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$order->setAttribute('available_actions', $actions);
|
||||
$order->setAttribute('is_editable', $order->status->isEditable());
|
||||
$order->setAttribute('is_editable', $this->isEditable($order->status));
|
||||
|
||||
return $order;
|
||||
});
|
||||
@ -239,12 +285,12 @@ public function findForShow(Order $order): Order
|
||||
}
|
||||
});
|
||||
|
||||
$availableActions = collect($order->status->availableActions())
|
||||
$availableActions = collect($this->availableActions($order->status))
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$order->setAttribute('available_actions', $availableActions);
|
||||
$order->setAttribute('is_editable', $order->status->isEditable());
|
||||
$order->setAttribute('is_editable', $this->isEditable($order->status));
|
||||
|
||||
return $order;
|
||||
}
|
||||
@ -453,7 +499,7 @@ public function create(array $validated, User $user): Order
|
||||
*/
|
||||
public function update(Order $order, array $validated): void
|
||||
{
|
||||
if (! $order->status->isEditable()) {
|
||||
if (! $this->isEditable($order->status)) {
|
||||
throw ValidationException::withMessages([
|
||||
'status' => 'Pesanan tidak dapat diubah.',
|
||||
]);
|
||||
@ -534,7 +580,7 @@ public function delete(Order $order): void
|
||||
DB::transaction(function () use ($order): void {
|
||||
$order->load('items');
|
||||
|
||||
if ($order->status->isEditable()) {
|
||||
if ($this->isEditable($order->status)) {
|
||||
foreach ($order->items as $item) {
|
||||
$this->incrementStock($item);
|
||||
}
|
||||
@ -558,7 +604,7 @@ public function delete(Order $order): void
|
||||
|
||||
public function transitionStatus(Order $order, OrderStatus $status): void
|
||||
{
|
||||
if (! $order->status->canTransitionTo($status)) {
|
||||
if (! $this->canTransitionTo($order->status, $status)) {
|
||||
throw ValidationException::withMessages([
|
||||
'status' => 'Status pesanan tidak dapat diubah.',
|
||||
]);
|
||||
@ -635,7 +681,7 @@ private function resolvePriceType(string $channel, string $priceType): PriceType
|
||||
$channelEnum = OrderChannel::from($channel);
|
||||
$priceTypeEnum = PriceType::from($priceType);
|
||||
|
||||
$defaultPriceType = $channelEnum->defaultPriceType();
|
||||
$defaultPriceType = $this->defaultPriceType($channelEnum);
|
||||
|
||||
if ($defaultPriceType !== null && $priceTypeEnum !== $defaultPriceType) {
|
||||
throw ValidationException::withMessages([
|
||||
@ -705,7 +751,7 @@ private function decrementStock(OrderItem $item): void
|
||||
|
||||
ProductVariant::query()
|
||||
->whereKey($item->product_variant_id)
|
||||
->decrement($stockQuality->stockColumn(), $item->quantity);
|
||||
->decrement($this->stockColumn($stockQuality), $item->quantity);
|
||||
}
|
||||
|
||||
private function incrementStock(OrderItem $item): void
|
||||
@ -714,12 +760,12 @@ private function incrementStock(OrderItem $item): void
|
||||
|
||||
ProductVariant::query()
|
||||
->whereKey($item->product_variant_id)
|
||||
->increment($stockQuality->stockColumn(), $item->quantity);
|
||||
->increment($this->stockColumn($stockQuality), $item->quantity);
|
||||
}
|
||||
|
||||
private function availableStock(ProductVariant $variant, ProductStockQuality $stockQuality): int
|
||||
{
|
||||
return (int) $variant->{$stockQuality->stockColumn()};
|
||||
return (int) $variant->{$this->stockColumn($stockQuality)};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -42,12 +42,18 @@ public function paginateForIndex(array $tableQuery, string $isActive, string $st
|
||||
$query->whereHas('prices', fn (Builder $priceQuery) => $priceQuery->where('stock', '<=', 0));
|
||||
})
|
||||
->when($stockStatus === 'low_stock', function (Builder $query): void {
|
||||
$query->whereHas('prices', function (Builder $priceQuery): void {
|
||||
$priceQuery->where('stock', '>', 0)->where(function (Builder $priceQuery): void {
|
||||
$minStock = [
|
||||
RawMaterialUnit::YARD => 20,
|
||||
RawMaterialUnit::METER => 10,
|
||||
RawMaterialUnit::KILOGRAM => 5,
|
||||
];
|
||||
|
||||
$query->whereHas('prices', function (Builder $priceQuery) use ($minStock): void {
|
||||
$priceQuery->where('stock', '>', 0)->where(function (Builder $priceQuery) use ($minStock): void {
|
||||
foreach (RawMaterialUnit::cases() as $unit) {
|
||||
$priceQuery->orWhere(function (Builder $priceQuery) use ($unit): void {
|
||||
$priceQuery->orWhere(function (Builder $priceQuery) use ($unit, $minStock): void {
|
||||
$priceQuery->whereHas('rawMaterial', fn (Builder $rawMaterialQuery) => $rawMaterialQuery->where('unit', $unit))
|
||||
->where('stock', '<', $unit->minStock());
|
||||
->where('stock', '<', $minStock[$unit]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -22,4 +22,12 @@ public static function selectOptions(): array
|
||||
])
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function values(): array
|
||||
{
|
||||
return array_column(static::cases(), 'value');
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,14 @@ class OrderFactory extends Factory
|
||||
public function definition(): array
|
||||
{
|
||||
$channel = fake()->randomElement(OrderChannel::cases());
|
||||
$priceType = $channel->defaultPriceType() ?? fake()->randomElement(PriceType::cases());
|
||||
|
||||
$defaultPriceType = match ($channel) {
|
||||
OrderChannel::SHOPEE => PriceType::SHOPEE,
|
||||
OrderChannel::TIKTOK => PriceType::TIKTOK,
|
||||
default => null,
|
||||
};
|
||||
|
||||
$priceType = $defaultPriceType ?? fake()->randomElement(PriceType::cases());
|
||||
$subtotal = fake()->numberBetween(100_000, 5_000_000);
|
||||
$discount = fake()->numberBetween(0, (int) ($subtotal * 0.15));
|
||||
|
||||
|
||||
@ -23,11 +23,15 @@ public function run(): void
|
||||
|
||||
$registrar->forgetCachedPermissions();
|
||||
|
||||
$rolePermissions = config('roles.permissions');
|
||||
|
||||
foreach (RoleEnum::cases() as $role) {
|
||||
$roleModel = Role::findOrCreate($role->value, 'web');
|
||||
|
||||
$rolePermissionsList = $rolePermissions[$role->value] ?? [];
|
||||
|
||||
$roleModel->syncPermissions(
|
||||
collect($role->permissions())
|
||||
collect($rolePermissionsList)
|
||||
->map(fn (PermissionEnum $permission) => $permissions[$permission->value])
|
||||
->all()
|
||||
);
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import type { AppearanceMode } from '@/types/account';
|
||||
import { AppearanceMode } from '@/constants/appearance-mode';
|
||||
import type { AppearanceMode as AppearanceModeType } from '@/types/account';
|
||||
|
||||
export function applyAppearance(mode: AppearanceMode): void {
|
||||
export function applyAppearance(mode: AppearanceModeType): void {
|
||||
const root = document.documentElement;
|
||||
|
||||
root.classList.remove('dark');
|
||||
|
||||
if (mode === 'dark' || (mode === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
if (mode === AppearanceMode.DARK || (mode === AppearanceMode.SYSTEM && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
root.classList.add('dark');
|
||||
}
|
||||
}
|
||||
|
||||
4
resources/js/constants/active-status.ts
Normal file
4
resources/js/constants/active-status.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const ActiveStatus = {
|
||||
ACTIVE: '1',
|
||||
INACTIVE: '0',
|
||||
} as const;
|
||||
5
resources/js/constants/appearance-mode.ts
Normal file
5
resources/js/constants/appearance-mode.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export const AppearanceMode = {
|
||||
LIGHT: 'light',
|
||||
DARK: 'dark',
|
||||
SYSTEM: 'system',
|
||||
} as const;
|
||||
7
resources/js/constants/cash-reference-type.ts
Normal file
7
resources/js/constants/cash-reference-type.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export const CashReferenceType = {
|
||||
MANUAL: 'manual',
|
||||
EXPENSE: 'App\\Models\\Expense',
|
||||
EMPLOYEE_ADVANCE: 'App\\Models\\EmployeeAdvance',
|
||||
PAYROLL: 'App\\Models\\Payroll',
|
||||
ORDER: 'App\\Models\\Order',
|
||||
} as const;
|
||||
4
resources/js/constants/cash-transaction-type.ts
Normal file
4
resources/js/constants/cash-transaction-type.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const CashTransactionType = {
|
||||
DEPOSIT: 'deposit',
|
||||
WITHDRAWAL: 'withdrawal',
|
||||
} as const;
|
||||
6
resources/js/constants/catalog-sort-option.ts
Normal file
6
resources/js/constants/catalog-sort-option.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export const CatalogSortOption = {
|
||||
DEFAULT: 'default',
|
||||
PRICE_ASC: 'price_asc',
|
||||
PRICE_DESC: 'price_desc',
|
||||
NAME_ASC: 'name_asc',
|
||||
} as const;
|
||||
6
resources/js/constants/cutting-status.ts
Normal file
6
resources/js/constants/cutting-status.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export const CuttingStatus = {
|
||||
IN_PROGRESS: 'in_progress',
|
||||
COMPLETED: 'completed',
|
||||
VERIFIED: 'verified',
|
||||
REJECTED: 'rejected',
|
||||
} as const;
|
||||
6
resources/js/constants/employee-advance-status.ts
Normal file
6
resources/js/constants/employee-advance-status.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export const EmployeeAdvanceStatus = {
|
||||
PENDING: 'pending',
|
||||
APPROVED: 'approved',
|
||||
REJECTED: 'rejected',
|
||||
PAID: 'paid',
|
||||
} as const;
|
||||
19
resources/js/constants/index.ts
Normal file
19
resources/js/constants/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export * from './order-channel';
|
||||
export * from './order-status';
|
||||
export * from './cutting-status';
|
||||
export * from './employee-advance-status';
|
||||
export * from './leave-request-status';
|
||||
export * from './payroll-status';
|
||||
export * from './cash-transaction-type';
|
||||
export * from './cash-reference-type';
|
||||
export * from './order-payment-type';
|
||||
export * from './stock-quality';
|
||||
export * from './stock-status';
|
||||
export * from './active-status';
|
||||
export * from './marketplace-platform';
|
||||
export * from './marketplace-fee-scope';
|
||||
export * from './marketplace-fee-value-type';
|
||||
export * from './setting-section';
|
||||
export * from './appearance-mode';
|
||||
export * from './catalog-sort-option';
|
||||
export * from './paper-size';
|
||||
5
resources/js/constants/leave-request-status.ts
Normal file
5
resources/js/constants/leave-request-status.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export const LeaveRequestStatus = {
|
||||
PENDING: 'pending',
|
||||
APPROVED: 'approved',
|
||||
REJECTED: 'rejected',
|
||||
} as const;
|
||||
4
resources/js/constants/marketplace-fee-scope.ts
Normal file
4
resources/js/constants/marketplace-fee-scope.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const MarketplaceFeeScope = {
|
||||
PRODUCT: 'product',
|
||||
TRANSACTION: 'transaction',
|
||||
} as const;
|
||||
4
resources/js/constants/marketplace-fee-value-type.ts
Normal file
4
resources/js/constants/marketplace-fee-value-type.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const MarketplaceFeeValueType = {
|
||||
FLAT: 'flat',
|
||||
PERCENT: 'percent',
|
||||
} as const;
|
||||
4
resources/js/constants/marketplace-platform.ts
Normal file
4
resources/js/constants/marketplace-platform.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const MarketplacePlatform = {
|
||||
TIKTOK_SHOP: 'tiktok_shop',
|
||||
SHOPEE: 'shopee',
|
||||
} as const;
|
||||
4
resources/js/constants/order-payment-type.ts
Normal file
4
resources/js/constants/order-payment-type.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const OrderPaymentType = {
|
||||
CASH: 'cash',
|
||||
MARKETPLACE: 'marketplace',
|
||||
} as const;
|
||||
5
resources/js/constants/order-status.ts
Normal file
5
resources/js/constants/order-status.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export const OrderStatus = {
|
||||
PROCESSING: 'processing',
|
||||
COMPLETED: 'completed',
|
||||
CANCELLED: 'cancelled',
|
||||
} as const;
|
||||
4
resources/js/constants/paper-size.ts
Normal file
4
resources/js/constants/paper-size.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const PaperSize = {
|
||||
MM_58: '58mm',
|
||||
MM_80: '80mm',
|
||||
} as const;
|
||||
4
resources/js/constants/payroll-status.ts
Normal file
4
resources/js/constants/payroll-status.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const PayrollStatus = {
|
||||
UNPAID: 'unpaid',
|
||||
PAID: 'paid',
|
||||
} as const;
|
||||
7
resources/js/constants/setting-section.ts
Normal file
7
resources/js/constants/setting-section.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export const SettingSection = {
|
||||
SYSTEM: 'system',
|
||||
SOCIAL: 'social',
|
||||
MARKETPLACE: 'marketplace',
|
||||
HR: 'hr',
|
||||
HOMEPAGE: 'homepage',
|
||||
} as const;
|
||||
4
resources/js/constants/stock-quality.ts
Normal file
4
resources/js/constants/stock-quality.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const StockQuality = {
|
||||
GOOD: 'good',
|
||||
REJECT: 'reject',
|
||||
} as const;
|
||||
4
resources/js/constants/stock-status.ts
Normal file
4
resources/js/constants/stock-status.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const StockStatus = {
|
||||
OUT_OF_STOCK: 'out_of_stock',
|
||||
LOW_STOCK: 'low_stock',
|
||||
} as const;
|
||||
@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { Home, Settings2, Share2, ShoppingBag, Users } from '@lucide/vue';
|
||||
import { SettingSection as SettingSectionConst } from '@/constants/setting-section';
|
||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { SettingSection } from '@/types/setting';
|
||||
@ -11,11 +12,11 @@ const navItems: Array<{
|
||||
label: string;
|
||||
icon: typeof Settings2;
|
||||
}> = [
|
||||
{ key: 'system', label: 'Sistem', icon: Settings2 },
|
||||
{ key: 'homepage', label: 'Homepage', icon: Home },
|
||||
{ key: 'social', label: 'Media Sosial', icon: Share2 },
|
||||
{ key: 'marketplace', label: 'Marketplace', icon: ShoppingBag },
|
||||
{ key: 'hr', label: 'HR / Pegawai', icon: Users },
|
||||
{ key: SettingSectionConst.SYSTEM, label: 'Sistem', icon: Settings2 },
|
||||
{ key: SettingSectionConst.HOMEPAGE, label: 'Homepage', icon: Home },
|
||||
{ key: SettingSectionConst.SOCIAL, label: 'Media Sosial', icon: Share2 },
|
||||
{ key: SettingSectionConst.MARKETPLACE, label: 'Marketplace', icon: ShoppingBag },
|
||||
{ key: SettingSectionConst.HR, label: 'HR / Pegawai', icon: Users },
|
||||
];
|
||||
</script>
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
Filter
|
||||
} from '@lucide/vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import { CatalogSortOption } from '@/constants/catalog-sort-option';
|
||||
import RupiahInput from '@/components/form/rupiah-input/RupiahInput.vue';
|
||||
import Select from '@/components/ui/select/Select.vue';
|
||||
import SelectContent from '@/components/ui/select/SelectContent.vue';
|
||||
@ -97,7 +98,7 @@ const searchInput = ref('');
|
||||
const selectedCategory = ref<string | null>(null);
|
||||
const minPrice = ref<number | null>(null);
|
||||
const maxPrice = ref<number | null>(null);
|
||||
const sortBy = ref<string>('default'); // 'default', 'price_asc', 'price_desc', 'name_asc'
|
||||
const sortBy = ref<string>(CatalogSortOption.DEFAULT);
|
||||
const showFilters = ref(false);
|
||||
|
||||
// Hardcoded order steps
|
||||
@ -155,11 +156,11 @@ const filteredProducts = computed(() => {
|
||||
});
|
||||
|
||||
// Sorting
|
||||
if (sortBy.value === 'price_asc') {
|
||||
if (sortBy.value === CatalogSortOption.PRICE_ASC) {
|
||||
result.sort((a, b) => getPrimaryEcerPrice(a) - getPrimaryEcerPrice(b));
|
||||
} else if (sortBy.value === 'price_desc') {
|
||||
} else if (sortBy.value === CatalogSortOption.PRICE_DESC) {
|
||||
result.sort((a, b) => getPrimaryEcerPrice(b) - getPrimaryEcerPrice(a));
|
||||
} else if (sortBy.value === 'name_asc') {
|
||||
} else if (sortBy.value === CatalogSortOption.NAME_ASC) {
|
||||
result.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
@ -359,7 +360,7 @@ return;
|
||||
const resetPriceFilters = () => {
|
||||
minPrice.value = null;
|
||||
maxPrice.value = null;
|
||||
sortBy.value = 'default';
|
||||
sortBy.value = CatalogSortOption.DEFAULT;
|
||||
};
|
||||
|
||||
|
||||
@ -645,10 +646,10 @@ const resetPriceFilters = () => {
|
||||
<SelectValue placeholder="Pilih urutan" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="default">Default (Urutan Utama)</SelectItem>
|
||||
<SelectItem value="price_asc">Harga Terendah</SelectItem>
|
||||
<SelectItem value="price_desc">Harga Tertinggi</SelectItem>
|
||||
<SelectItem value="name_asc">Nama Produk (A-Z)</SelectItem>
|
||||
<SelectItem :value="CatalogSortOption.DEFAULT">Default (Urutan Utama)</SelectItem>
|
||||
<SelectItem :value="CatalogSortOption.PRICE_ASC">Harga Terendah</SelectItem>
|
||||
<SelectItem :value="CatalogSortOption.PRICE_DESC">Harga Tertinggi</SelectItem>
|
||||
<SelectItem :value="CatalogSortOption.NAME_ASC">Nama Produk (A-Z)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||
import { applyAppearance } from '@/composables/useAppearance';
|
||||
import { AppearanceMode as AppearanceModeConst } from '@/constants/appearance-mode';
|
||||
import AccountLayout from '@/layouts/AccountLayout.vue';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import { cn } from '@/lib/utils';
|
||||
@ -33,19 +34,19 @@ const options: Array<{
|
||||
icon: typeof Sun;
|
||||
}> = [
|
||||
{
|
||||
value: 'light',
|
||||
value: AppearanceModeConst.LIGHT,
|
||||
label: 'Terang',
|
||||
description: 'Tampilan terang untuk lingkungan yang cukup pencahayaan.',
|
||||
icon: Sun,
|
||||
},
|
||||
{
|
||||
value: 'dark',
|
||||
value: AppearanceModeConst.DARK,
|
||||
label: 'Gelap',
|
||||
description: 'Tampilan gelap yang nyaman di malam hari.',
|
||||
icon: Moon,
|
||||
},
|
||||
{
|
||||
value: 'system',
|
||||
value: AppearanceModeConst.SYSTEM,
|
||||
label: 'Sistem',
|
||||
description: 'Mengikuti pengaturan tampilan perangkat Anda.',
|
||||
icon: Monitor,
|
||||
|
||||
@ -2,16 +2,18 @@
|
||||
import { Head } from '@inertiajs/vue3';
|
||||
import { ArrowDownCircle, ArrowUpCircle, Wallet } from '@lucide/vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import CashTransactionFormModal from './form/CashTransactionFormModal.vue';
|
||||
import { createColumns } from './table/columns';
|
||||
import { DataTable } from '@/components/data-table';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { useCan } from '@/composables/useCan';
|
||||
import { CashReferenceType } from '@/constants/cash-reference-type';
|
||||
import { CashTransactionType } from '@/constants/cash-transaction-type';
|
||||
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
|
||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||
import type { CashAccount, CashTransactionListItem, PaginatedCashTransactions } from '@/types/cash';
|
||||
import type { DataTableFilterDef, DataTableSort } from '@/types/data-table';
|
||||
import CashTransactionFormModal from './form/CashTransactionFormModal.vue';
|
||||
import { createColumns } from './table/columns';
|
||||
|
||||
const props = defineProps<{
|
||||
cashAccount: CashAccount;
|
||||
@ -27,7 +29,7 @@ const props = defineProps<{
|
||||
const { can } = useCan();
|
||||
const search = ref(props.filters.search ?? '');
|
||||
const formModalOpen = ref(false);
|
||||
const formModalMode = ref<'deposit' | 'withdrawal'>('deposit');
|
||||
const formModalMode = ref<'deposit' | 'withdrawal'>(CashTransactionType.DEPOSIT);
|
||||
const editingTransaction = ref<CashTransactionListItem | null>(null);
|
||||
|
||||
const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } = useDataTableQuery({
|
||||
@ -57,11 +59,11 @@ const filterDefs = computed<DataTableFilterDef[]>(() => [
|
||||
label: 'Sumber',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: 'manual', label: 'Setor/Tarik Kas' },
|
||||
{ value: 'App\\Models\\Expense', label: 'Pengeluaran' },
|
||||
{ value: 'App\\Models\\EmployeeAdvance', label: 'Kasbon Pegawai' },
|
||||
{ value: 'App\\Models\\Payroll', label: 'Gaji Pegawai' },
|
||||
{ value: 'App\\Models\\Order', label: 'Pesanan' },
|
||||
{ value: CashReferenceType.MANUAL, label: 'Setor/Tarik Kas' },
|
||||
{ value: CashReferenceType.EXPENSE, label: 'Pengeluaran' },
|
||||
{ value: CashReferenceType.EMPLOYEE_ADVANCE, label: 'Kasbon Pegawai' },
|
||||
{ value: CashReferenceType.PAYROLL, label: 'Gaji Pegawai' },
|
||||
{ value: CashReferenceType.ORDER, label: 'Pesanan' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
@ -77,7 +79,7 @@ const pagination = computed(() => ({
|
||||
total: props.transactions.total,
|
||||
}));
|
||||
|
||||
function openCreateModal(mode: 'deposit' | 'withdrawal') {
|
||||
function openCreateModal(mode: typeof CashTransactionType.DEPOSIT | typeof CashTransactionType.WITHDRAWAL) {
|
||||
editingTransaction.value = null;
|
||||
formModalMode.value = mode;
|
||||
formModalOpen.value = true;
|
||||
@ -113,11 +115,11 @@ watch(
|
||||
</div>
|
||||
|
||||
<div class="flex shrink-0 items-center gap-2 self-start sm:self-center">
|
||||
<Button v-if="can('cash.deposit')" variant="outline" @click="openCreateModal('deposit')">
|
||||
<Button v-if="can('cash.deposit')" variant="outline" @click="openCreateModal(CashTransactionType.DEPOSIT)">
|
||||
<ArrowDownCircle class="size-4" />
|
||||
Setor Kas
|
||||
</Button>
|
||||
<Button v-if="can('cash.withdraw')" variant="outline" @click="openCreateModal('withdrawal')">
|
||||
<Button v-if="can('cash.withdraw')" variant="outline" @click="openCreateModal(CashTransactionType.WITHDRAWAL)">
|
||||
<ArrowUpCircle class="size-4" />
|
||||
Tarik Kas
|
||||
</Button>
|
||||
|
||||
@ -22,6 +22,7 @@ import {
|
||||
} from '@/components/ui/field';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useFormDialog } from '@/composables/useFormDialog';
|
||||
import { CashTransactionType } from '@/constants/cash-transaction-type';
|
||||
import { FIELD_LIMITS } from '@/lib/field-limits';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import { parseRupiah } from '@/lib/rupiah';
|
||||
@ -41,9 +42,9 @@ const currentMode = computed(() => {
|
||||
return props.transaction.type;
|
||||
}
|
||||
|
||||
return props.mode ?? 'deposit';
|
||||
return props.mode ?? CashTransactionType.DEPOSIT;
|
||||
});
|
||||
const isWithdrawal = computed(() => currentMode.value === 'withdrawal');
|
||||
const isWithdrawal = computed(() => currentMode.value === CashTransactionType.WITHDRAWAL);
|
||||
|
||||
const existingPhotoId = ref<number | null>(null);
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import { DataTable } from '@/components/data-table';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { useCan } from '@/composables/useCan';
|
||||
import { EmployeeAdvanceStatus } from '@/constants/employee-advance-status';
|
||||
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
|
||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||
import type { DataTableFilterDef, DataTableSort } from '@/types/data-table';
|
||||
@ -54,10 +55,10 @@ const filterDefs = computed<DataTableFilterDef[]>(() => [
|
||||
label: 'Status',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: 'pending', label: 'Menunggu' },
|
||||
{ value: 'approved', label: 'Disetujui' },
|
||||
{ value: 'rejected', label: 'Ditolak' },
|
||||
{ value: 'paid', label: 'Lunas' },
|
||||
{ value: EmployeeAdvanceStatus.PENDING, label: 'Menunggu' },
|
||||
{ value: EmployeeAdvanceStatus.APPROVED, label: 'Disetujui' },
|
||||
{ value: EmployeeAdvanceStatus.REJECTED, label: 'Ditolak' },
|
||||
{ value: EmployeeAdvanceStatus.PAID, label: 'Lunas' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
@ -2,16 +2,17 @@ import type { ColumnDef } from '@tanstack/vue-table';
|
||||
import { h } from 'vue';
|
||||
import { DataTableColumnHeader } from '@/components/data-table';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { EmployeeAdvanceStatus } from '@/constants/employee-advance-status';
|
||||
import type { EmployeeAdvanceListItem } from '@/types/employee-advance';
|
||||
import DataTableActions from './data-table-actions.vue';
|
||||
|
||||
function statusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' {
|
||||
switch (status) {
|
||||
case 'approved':
|
||||
case EmployeeAdvanceStatus.APPROVED:
|
||||
return 'default';
|
||||
case 'paid':
|
||||
case EmployeeAdvanceStatus.PAID:
|
||||
return 'secondary';
|
||||
case 'rejected':
|
||||
case EmployeeAdvanceStatus.REJECTED:
|
||||
return 'destructive';
|
||||
default:
|
||||
return 'outline';
|
||||
|
||||
@ -2,11 +2,12 @@ import type { ColumnDef } from '@tanstack/vue-table';
|
||||
import { h } from 'vue';
|
||||
import { DataTableColumnHeader } from '@/components/data-table';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { PayrollStatus } from '@/constants/payroll-status';
|
||||
import type { PayrollListItem } from '@/types/payroll';
|
||||
import DataTableActions from './data-table-actions.vue';
|
||||
|
||||
function statusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' {
|
||||
return status === 'paid' ? 'secondary' : 'outline';
|
||||
return status === PayrollStatus.PAID ? 'secondary' : 'outline';
|
||||
}
|
||||
|
||||
export function createColumns(
|
||||
|
||||
@ -7,6 +7,7 @@ import { DataTable } from '@/components/data-table';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useCan } from '@/composables/useCan';
|
||||
import { ActiveStatus } from '@/constants/active-status';
|
||||
import {
|
||||
useDataTableQuery,
|
||||
useDataTableQuerySync,
|
||||
@ -66,8 +67,8 @@ const filterDefs = computed<DataTableFilterDef[]>(() => [
|
||||
label: 'Status Akun',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: '1', label: 'Aktif' },
|
||||
{ value: '0', label: 'Nonaktif' },
|
||||
{ value: ActiveStatus.ACTIVE, label: 'Aktif' },
|
||||
{ value: ActiveStatus.INACTIVE, label: 'Nonaktif' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
@ -2,14 +2,15 @@ import type { ColumnDef } from '@tanstack/vue-table';
|
||||
import { h } from 'vue';
|
||||
import { DataTableColumnHeader } from '@/components/data-table';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { LeaveRequestStatus } from '@/constants/leave-request-status';
|
||||
import type { LeaveRequestListItem } from '@/types/leave-request';
|
||||
import DataTableActions from './data-table-actions.vue';
|
||||
|
||||
function statusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' {
|
||||
switch (status) {
|
||||
case 'approved':
|
||||
case LeaveRequestStatus.APPROVED:
|
||||
return 'default';
|
||||
case 'rejected':
|
||||
case LeaveRequestStatus.REJECTED:
|
||||
return 'destructive';
|
||||
default:
|
||||
return 'outline';
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { computed } from 'vue';
|
||||
import DataTableActions from './data-table-actions.vue';
|
||||
import { CuttingStatus } from '@/constants/cutting-status';
|
||||
import DataTableToolbar from '@/components/data-table/DataTableToolbar.vue';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@ -59,15 +60,15 @@ function rowNumber(index: number): number {
|
||||
}
|
||||
|
||||
function statusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' {
|
||||
if (status === 'verified') {
|
||||
if (status === CuttingStatus.VERIFIED) {
|
||||
return 'default';
|
||||
}
|
||||
|
||||
if (status === 'rejected') {
|
||||
if (status === CuttingStatus.REJECTED) {
|
||||
return 'destructive';
|
||||
}
|
||||
|
||||
if (status === 'completed') {
|
||||
if (status === CuttingStatus.COMPLETED) {
|
||||
return 'secondary';
|
||||
}
|
||||
|
||||
|
||||
@ -30,6 +30,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { useCan } from '@/composables/useCan';
|
||||
import { CuttingStatus } from '@/constants/cutting-status';
|
||||
import { RupiahInput } from '@/components/form/rupiah-input';
|
||||
import { parseRupiah } from '@/lib/rupiah';
|
||||
import {
|
||||
@ -54,7 +55,7 @@ const allMatches = ref(true);
|
||||
const pendingAction = ref<CuttingStatusAction | null>(null);
|
||||
|
||||
const rejectForm = useForm({
|
||||
status: 'rejected',
|
||||
status: CuttingStatus.REJECTED,
|
||||
reason: '',
|
||||
});
|
||||
|
||||
@ -63,7 +64,7 @@ function buildEmptyPrices(): Record<string, string> {
|
||||
}
|
||||
|
||||
const verifyForm = useForm({
|
||||
status: 'verified',
|
||||
status: CuttingStatus.VERIFIED,
|
||||
verification_note: '',
|
||||
results: props.cutting.results.map(res => ({
|
||||
product_variant_id: res.product_variant?.id || 0,
|
||||
@ -151,7 +152,7 @@ const canEdit = computed(
|
||||
() => props.cutting.is_editable && can('cuttings.update'),
|
||||
);
|
||||
const canDelete = computed(
|
||||
() => can('cuttings.delete') && ['in_progress', 'rejected'].includes(props.cutting.status),
|
||||
() => can('cuttings.delete') && [CuttingStatus.IN_PROGRESS, CuttingStatus.REJECTED].includes(props.cutting.status),
|
||||
);
|
||||
|
||||
function canPerformAction(action: CuttingStatusAction): boolean {
|
||||
@ -159,15 +160,15 @@ function canPerformAction(action: CuttingStatusAction): boolean {
|
||||
}
|
||||
|
||||
function statusConfirmDescription(action: CuttingStatusAction): string {
|
||||
if (action.status === 'completed') {
|
||||
if (action.status === CuttingStatus.COMPLETED) {
|
||||
return 'Cutting akan ditandai selesai. Stok bahan baku akan dipotong dan sisa dikembalikan. Menunggu verifikasi admin toko.';
|
||||
}
|
||||
|
||||
if (action.status === 'verified') {
|
||||
if (action.status === CuttingStatus.VERIFIED) {
|
||||
return 'Hasil cutting akan diverifikasi. Stok bagus dan reject akan ditambahkan ke produk.';
|
||||
}
|
||||
|
||||
if (action.status === 'in_progress') {
|
||||
if (action.status === CuttingStatus.IN_PROGRESS) {
|
||||
return 'Cutting dikembalikan ke proses untuk diperbaiki.';
|
||||
}
|
||||
|
||||
@ -175,13 +176,13 @@ function statusConfirmDescription(action: CuttingStatusAction): string {
|
||||
}
|
||||
|
||||
function openStatusConfirm(action: CuttingStatusAction) {
|
||||
if (action.status === 'verified') {
|
||||
if (action.status === CuttingStatus.VERIFIED) {
|
||||
verifyDialogOpen.value = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.status === 'rejected') {
|
||||
if (action.status === CuttingStatus.REJECTED) {
|
||||
rejectForm.reset();
|
||||
rejectForm.clearErrors();
|
||||
rejectDialogOpen.value = true;
|
||||
@ -299,15 +300,15 @@ function destroyCutting() {
|
||||
}
|
||||
|
||||
function actionIcon(status: string) {
|
||||
if (status === 'completed') {
|
||||
if (status === CuttingStatus.COMPLETED) {
|
||||
return Scissors;
|
||||
}
|
||||
|
||||
if (status === 'verified') {
|
||||
if (status === CuttingStatus.VERIFIED) {
|
||||
return Check;
|
||||
}
|
||||
|
||||
if (status === 'in_progress') {
|
||||
if (status === CuttingStatus.IN_PROGRESS) {
|
||||
return RotateCcw;
|
||||
}
|
||||
|
||||
|
||||
@ -32,6 +32,10 @@ import {
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { useCan } from '@/composables/useCan';
|
||||
import { MarketplaceFeeScope } from '@/constants/marketplace-fee-scope';
|
||||
import { MarketplaceFeeValueType } from '@/constants/marketplace-fee-value-type';
|
||||
import { OrderStatus } from '@/constants/order-status';
|
||||
import { StockQuality } from '@/constants/stock-quality';
|
||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||
import type { OrderDetail, OrderStatusAction } from '@/types/order';
|
||||
|
||||
@ -59,15 +63,15 @@ const marketingName = computed(() => {
|
||||
});
|
||||
|
||||
function statusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' {
|
||||
if (status === 'completed') return 'default';
|
||||
if (status === 'cancelled') return 'destructive';
|
||||
if (status === 'processing') return 'secondary';
|
||||
if (status === OrderStatus.COMPLETED) return 'default';
|
||||
if (status === OrderStatus.CANCELLED) return 'destructive';
|
||||
if (status === OrderStatus.PROCESSING) return 'secondary';
|
||||
return 'outline';
|
||||
}
|
||||
|
||||
function actionIcon(status: string) {
|
||||
if (status === 'processing') return Send;
|
||||
if (status === 'completed') return Check;
|
||||
if (status === OrderStatus.PROCESSING) return Send;
|
||||
if (status === OrderStatus.COMPLETED) return Check;
|
||||
return X;
|
||||
}
|
||||
|
||||
@ -76,10 +80,10 @@ function canPerformAction(action: OrderStatusAction): boolean {
|
||||
}
|
||||
|
||||
function statusConfirmDescription(action: OrderStatusAction): string {
|
||||
if (action.status === 'processing') {
|
||||
if (action.status === OrderStatus.PROCESSING) {
|
||||
return `Pesanan ${props.order.order_number} akan dikirim dan diproses.`;
|
||||
}
|
||||
if (action.status === 'completed') {
|
||||
if (action.status === OrderStatus.COMPLETED) {
|
||||
return `Pesanan ${props.order.order_number} akan ditandai selesai.`;
|
||||
}
|
||||
return `Pesanan ${props.order.order_number} akan dibatalkan. Stok produk akan dikembalikan.`;
|
||||
@ -182,9 +186,9 @@ function formatRp(amount: number): string {
|
||||
}
|
||||
|
||||
function formatFeeRule(fee: { scope: string; value_type: string; value: number }): string {
|
||||
const scope = fee.scope === 'product' ? '/produk' : '/transaksi';
|
||||
const scope = fee.scope === MarketplaceFeeScope.PRODUCT ? '/produk' : '/transaksi';
|
||||
|
||||
if (fee.value_type === 'percent') {
|
||||
if (fee.value_type === MarketplaceFeeValueType.PERCENT) {
|
||||
return `${fee.value}%${scope}`;
|
||||
}
|
||||
|
||||
@ -374,7 +378,7 @@ const feeEntries = computed(() => {
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline" class="text-xs">
|
||||
{{ item.stock_quality === 'reject' ? 'Reject' : 'Bagus' }}
|
||||
{{ item.stock_quality === StockQuality.REJECT ? 'Reject' : 'Bagus' }}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell class="text-right tabular-nums">
|
||||
|
||||
@ -41,6 +41,9 @@ import { Switch } from '@/components/ui/switch';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useCan } from '@/composables/useCan';
|
||||
import { OrderChannel } from '@/constants/order-channel';
|
||||
import { OrderPaymentType } from '@/constants/order-payment-type';
|
||||
import { PaperSize } from '@/constants/paper-size';
|
||||
import { StockQuality } from '@/constants/stock-quality';
|
||||
import { apiFetch } from '@/lib/api';
|
||||
import { getFirstCoverImage } from '@/lib/catalog-cover';
|
||||
import { formErrors } from '@/lib/form';
|
||||
@ -95,12 +98,12 @@ const isMarketingUser = computed(() =>
|
||||
);
|
||||
|
||||
const search = ref('');
|
||||
const selectedStockQuality = ref<'good' | 'reject'>('good');
|
||||
const selectedStockQuality = ref<'good' | 'reject'>(StockQuality.GOOD);
|
||||
const cart = ref<OrderCartItem[]>([]);
|
||||
const customerFormOpen = ref(false);
|
||||
const cartDetailOpen = ref(false);
|
||||
const printAfterSave = ref(false);
|
||||
const selectedPaperSize = ref<'58mm' | '80mm'>('80mm');
|
||||
const selectedPaperSize = ref<'58mm' | '80mm'>(PaperSize.MM_80);
|
||||
|
||||
function onCustomerCreated(customer: { id: number; name: string }) {
|
||||
emit('customer-created', customer);
|
||||
@ -117,7 +120,7 @@ const form = useForm({
|
||||
marketing_id: defaultMarketingId.value || 'none',
|
||||
channel: 'store',
|
||||
price_type: 'ecer',
|
||||
payment_type: 'cash',
|
||||
payment_type: OrderPaymentType.CASH,
|
||||
is_affiliate: false,
|
||||
tiktok_order_id: '',
|
||||
shopee_order_id: '',
|
||||
@ -127,7 +130,7 @@ const form = useForm({
|
||||
});
|
||||
|
||||
const isStoreChannel = computed(() => form.channel === 'store');
|
||||
const isMarketplaceChannel = computed(() => form.channel === 'shopee' || form.channel === 'tiktok');
|
||||
const isMarketplaceChannel = computed(() => form.channel === OrderChannel.SHOPEE || form.channel === OrderChannel.TIKTOK);
|
||||
|
||||
function populateForm() {
|
||||
if (!props.initialData) {
|
||||
@ -147,7 +150,7 @@ function populateForm() {
|
||||
form.notes = props.initialData.notes;
|
||||
cart.value = props.initialData.items.map((item) => ({
|
||||
...item,
|
||||
stock_quality: item.stock_quality ?? 'good',
|
||||
stock_quality: item.stock_quality ?? StockQuality.GOOD,
|
||||
}));
|
||||
}
|
||||
|
||||
@ -172,12 +175,12 @@ populateDraftItems();
|
||||
watch(
|
||||
() => form.channel,
|
||||
(channel) => {
|
||||
if (channel === 'shopee') {
|
||||
if (channel === OrderChannel.SHOPEE) {
|
||||
form.price_type = 'shopee';
|
||||
form.payment_type = 'marketplace';
|
||||
} else if (channel === 'tiktok') {
|
||||
form.payment_type = OrderPaymentType.MARKETPLACE;
|
||||
} else if (channel === OrderChannel.TIKTOK) {
|
||||
form.price_type = 'tiktok';
|
||||
form.payment_type = 'marketplace';
|
||||
form.payment_type = OrderPaymentType.MARKETPLACE;
|
||||
} else if (!props.storePriceTypes.some((type) => type.value === form.price_type)) {
|
||||
form.price_type = 'ecer';
|
||||
}
|
||||
@ -228,7 +231,7 @@ function upsertCartItem(item: OrderCartItem) {
|
||||
}
|
||||
|
||||
function availableStockForVariant(variant: ProductVariantItem, stockQuality: string): number {
|
||||
return stockQuality === 'reject' ? variant.reject_stock : variant.stock;
|
||||
return stockQuality === StockQuality.REJECT ? variant.reject_stock : variant.stock;
|
||||
}
|
||||
|
||||
const filteredCatalog = computed(() => {
|
||||
@ -309,7 +312,7 @@ async function addToCart(product: OrderCatalogItem, variant: ProductVariantItem)
|
||||
const availableStock = availableStockForVariant(variant, stockQuality);
|
||||
|
||||
if (availableStock < 1) {
|
||||
toast.error(`Stok ${stockQuality === 'reject' ? 'reject' : 'bagus'} tidak tersedia.`);
|
||||
toast.error(`Stok ${stockQuality === StockQuality.REJECT ? 'reject' : 'bagus'} tidak tersedia.`);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -322,7 +325,7 @@ async function addToCart(product: OrderCatalogItem, variant: ProductVariantItem)
|
||||
const nextQty = existing ? (Number(existing.quantity) || 0) + 1 : 1;
|
||||
|
||||
if (nextQty > availableStock) {
|
||||
toast.error(`Stok ${stockQuality === 'reject' ? 'reject' : 'bagus'} tidak mencukupi.`);
|
||||
toast.error(`Stok ${stockQuality === StockQuality.REJECT ? 'reject' : 'bagus'} tidak mencukupi.`);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -441,15 +444,15 @@ function buildFormData(): FormData {
|
||||
formData.append('price_type', form.price_type);
|
||||
formData.append('payment_type', form.payment_type);
|
||||
|
||||
if (form.channel !== 'store') {
|
||||
if (!isStoreChannel.value) {
|
||||
formData.append('is_affiliate', form.is_affiliate ? '1' : '0');
|
||||
}
|
||||
|
||||
if (form.channel === 'tiktok' && form.tiktok_order_id) {
|
||||
if (form.channel === OrderChannel.TIKTOK && form.tiktok_order_id) {
|
||||
formData.append('tiktok_order_id', form.tiktok_order_id);
|
||||
}
|
||||
|
||||
if (form.channel === 'shopee' && form.shopee_order_id) {
|
||||
if (form.channel === OrderChannel.SHOPEE && form.shopee_order_id) {
|
||||
formData.append('shopee_order_id', form.shopee_order_id);
|
||||
}
|
||||
|
||||
@ -645,7 +648,7 @@ function submit() {
|
||||
</div>
|
||||
</Field>
|
||||
|
||||
<Field v-if="form.channel === 'tiktok'">
|
||||
<Field v-if="form.channel === OrderChannel.TIKTOK">
|
||||
<FieldLabel for="tiktok_order_id" :required="form.channel === OrderChannel.TIKTOK">ID
|
||||
Pesanan
|
||||
TikTok Shop</FieldLabel>
|
||||
@ -654,7 +657,7 @@ function submit() {
|
||||
<FieldError :errors="formErrors(form, 'tiktok_order_id')" />
|
||||
</Field>
|
||||
|
||||
<Field v-if="form.channel === 'shopee'">
|
||||
<Field v-if="form.channel === OrderChannel.SHOPEE">
|
||||
<FieldLabel for="shopee_order_id" :required="form.channel === OrderChannel.SHOPEE">ID
|
||||
Pesanan
|
||||
Shopee</FieldLabel>
|
||||
@ -763,7 +766,7 @@ function submit() {
|
||||
<p class="truncate text-xs text-muted-foreground">
|
||||
{{ item.variant_name }}
|
||||
·
|
||||
{{ item.stock_quality_label ?? (item.stock_quality === 'reject'
|
||||
{{ item.stock_quality_label ?? (item.stock_quality === StockQuality.REJECT
|
||||
? 'Reject' : 'Bagus') }}
|
||||
</p>
|
||||
</div>
|
||||
@ -843,7 +846,7 @@ function submit() {
|
||||
<div v-if="printAfterSave" class="mt-2 flex items-center gap-2 pl-1">
|
||||
<span class="text-xs text-muted-foreground">Ukuran kertas:</span>
|
||||
<div class="flex gap-1.5">
|
||||
<button v-for="size in (['58mm', '80mm'] as const)" :key="size" type="button"
|
||||
<button v-for="size in ([PaperSize.MM_58, PaperSize.MM_80] as const)" :key="size" type="button"
|
||||
class="inline-flex h-7 items-center rounded-md border px-2.5 text-xs font-medium transition-colors cursor-pointer"
|
||||
:class="selectedPaperSize === size
|
||||
? 'border-primary bg-primary/5 text-primary'
|
||||
@ -881,7 +884,7 @@ function submit() {
|
||||
<p class="truncate text-sm font-medium">{{ item.product_name }}</p>
|
||||
<p class="truncate text-xs text-muted-foreground">
|
||||
{{ item.variant_name }}
|
||||
· {{ item.stock_quality_label ?? (item.stock_quality === 'reject' ? 'Reject' : 'Bagus')
|
||||
· {{ item.stock_quality_label ?? (item.stock_quality === StockQuality.REJECT ? 'Reject' : 'Bagus')
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -3,6 +3,7 @@ import { Link } from '@inertiajs/vue3';
|
||||
import { computed } from 'vue';
|
||||
import DataTableActions from './data-table-actions.vue';
|
||||
import { formatRupiah } from '@/lib/rupiah';
|
||||
import { OrderStatus } from '@/constants/order-status';
|
||||
import DataTableToolbar from '@/components/data-table/DataTableToolbar.vue';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@ -60,15 +61,15 @@ function rowNumber(index: number): number {
|
||||
}
|
||||
|
||||
function statusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' {
|
||||
if (status === 'completed') {
|
||||
if (status === OrderStatus.COMPLETED) {
|
||||
return 'default';
|
||||
}
|
||||
|
||||
if (status === 'cancelled') {
|
||||
if (status === OrderStatus.CANCELLED) {
|
||||
return 'destructive';
|
||||
}
|
||||
|
||||
if (status === 'processing') {
|
||||
if (status === OrderStatus.PROCESSING) {
|
||||
return 'secondary';
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import ConfirmDialog from '@/components/ConfirmDialog.vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { useCan } from '@/composables/useCan';
|
||||
import { OrderStatus } from '@/constants/order-status';
|
||||
import type { OrderListItem, OrderStatusAction } from '@/types/order';
|
||||
|
||||
const props = defineProps<{
|
||||
@ -32,11 +33,11 @@ function canPerformAction(action: OrderStatusAction): boolean {
|
||||
}
|
||||
|
||||
function statusConfirmDescription(action: OrderStatusAction): string {
|
||||
if (action.status === 'processing') {
|
||||
if (action.status === OrderStatus.PROCESSING) {
|
||||
return `Pesanan ${props.order.order_number} akan dikirim dan diproses.`;
|
||||
}
|
||||
|
||||
if (action.status === 'completed') {
|
||||
if (action.status === OrderStatus.COMPLETED) {
|
||||
return `Pesanan ${props.order.order_number} akan ditandai selesai.`;
|
||||
}
|
||||
|
||||
@ -92,11 +93,11 @@ function destroyOrder() {
|
||||
}
|
||||
|
||||
function actionIcon(status: string) {
|
||||
if (status === 'processing') {
|
||||
if (status === OrderStatus.PROCESSING) {
|
||||
return Send;
|
||||
}
|
||||
|
||||
if (status === 'completed') {
|
||||
if (status === OrderStatus.COMPLETED) {
|
||||
return Check;
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,8 @@ import { computed, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useCan } from '@/composables/useCan';
|
||||
import { ActiveStatus } from '@/constants/active-status';
|
||||
import { StockStatus } from '@/constants/stock-status';
|
||||
import {
|
||||
useDataTableQuery,
|
||||
useDataTableQuerySync,
|
||||
@ -54,8 +56,8 @@ const filterDefs = computed<DataTableFilterDef[]>(() => [
|
||||
label: 'Status',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: '1', label: 'Aktif' },
|
||||
{ value: '0', label: 'Nonaktif' },
|
||||
{ value: ActiveStatus.ACTIVE, label: 'Aktif' },
|
||||
{ value: ActiveStatus.INACTIVE, label: 'Nonaktif' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -63,8 +65,8 @@ const filterDefs = computed<DataTableFilterDef[]>(() => [
|
||||
label: 'Stok',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: 'out_of_stock', label: 'Stok Habis' },
|
||||
{ value: 'low_stock', label: 'Stok Menipis' },
|
||||
{ value: StockStatus.OUT_OF_STOCK, label: 'Stok Habis' },
|
||||
{ value: StockStatus.LOW_STOCK, label: 'Stok Menipis' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
@ -5,6 +5,8 @@ import { computed, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useCan } from '@/composables/useCan';
|
||||
import { ActiveStatus } from '@/constants/active-status';
|
||||
import { StockStatus } from '@/constants/stock-status';
|
||||
import {
|
||||
useDataTableQuery,
|
||||
useDataTableQuerySync,
|
||||
@ -43,8 +45,8 @@ const filterDefs = computed<DataTableFilterDef[]>(() => [
|
||||
label: 'Status',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: '1', label: 'Aktif' },
|
||||
{ value: '0', label: 'Nonaktif' },
|
||||
{ value: ActiveStatus.ACTIVE, label: 'Aktif' },
|
||||
{ value: ActiveStatus.INACTIVE, label: 'Nonaktif' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -52,8 +54,8 @@ const filterDefs = computed<DataTableFilterDef[]>(() => [
|
||||
label: 'Stok',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: 'out_of_stock', label: 'Stok Habis' },
|
||||
{ value: 'low_stock', label: 'Stok Menipis' },
|
||||
{ value: StockStatus.OUT_OF_STOCK, label: 'Stok Habis' },
|
||||
{ value: StockStatus.LOW_STOCK, label: 'Stok Menipis' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
@ -6,6 +6,7 @@ import HrSection from './form/HrSection.vue';
|
||||
import MarketplaceSection from './form/MarketplaceSection.vue';
|
||||
import SocialMediaSection from './form/SocialMediaSection.vue';
|
||||
import SystemSection from './form/SystemSection.vue';
|
||||
import { SettingSection as SettingSectionConst } from '@/constants/setting-section';
|
||||
import SettingLayout from '@/layouts/SettingLayout.vue';
|
||||
import type {
|
||||
HomepageSettingsData,
|
||||
@ -24,7 +25,7 @@ defineProps<{
|
||||
homepage: HomepageSettingsData;
|
||||
}>();
|
||||
|
||||
const activeSection = ref<SettingSection>('system');
|
||||
const activeSection = ref<SettingSection>(SettingSectionConst.SYSTEM);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -32,10 +33,10 @@ const activeSection = ref<SettingSection>('system');
|
||||
<Head title="Pengaturan" />
|
||||
|
||||
<SettingLayout v-model:section="activeSection">
|
||||
<SystemSection v-if="activeSection === 'system'" :data="system" />
|
||||
<HomepageSection v-else-if="activeSection === 'homepage'" :data="homepage" />
|
||||
<SocialMediaSection v-else-if="activeSection === 'social'" :data="socialMedia" />
|
||||
<MarketplaceSection v-else-if="activeSection === 'marketplace'" :data="marketplace" />
|
||||
<HrSection v-else-if="activeSection === 'hr'" :data="hr" />
|
||||
<SystemSection v-if="activeSection === SettingSectionConst.SYSTEM" :data="system" />
|
||||
<HomepageSection v-else-if="activeSection === SettingSectionConst.HOMEPAGE" :data="homepage" />
|
||||
<SocialMediaSection v-else-if="activeSection === SettingSectionConst.SOCIAL" :data="socialMedia" />
|
||||
<MarketplaceSection v-else-if="activeSection === SettingSectionConst.MARKETPLACE" :data="marketplace" />
|
||||
<HrSection v-else-if="activeSection === SettingSectionConst.HR" :data="hr" />
|
||||
</SettingLayout>
|
||||
</template>
|
||||
|
||||
@ -14,6 +14,8 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { MarketplaceFeeScope } from '@/constants/marketplace-fee-scope';
|
||||
import { MarketplaceFeeValueType } from '@/constants/marketplace-fee-value-type';
|
||||
import type { MarketplaceFeeRule } from '@/types/setting';
|
||||
|
||||
const props = defineProps<{
|
||||
@ -27,7 +29,7 @@ const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: MarketplaceFeeRule): void;
|
||||
}>();
|
||||
|
||||
const isPercent = computed(() => props.modelValue.value_type === 'percent');
|
||||
const isPercent = computed(() => props.modelValue.value_type === MarketplaceFeeValueType.PERCENT);
|
||||
|
||||
function update(partial: Partial<MarketplaceFeeRule>) {
|
||||
emit('update:modelValue', {
|
||||
@ -38,7 +40,7 @@ function update(partial: Partial<MarketplaceFeeRule>) {
|
||||
|
||||
function updateValue(value: string | number) {
|
||||
update({
|
||||
value: props.modelValue.value_type === 'flat'
|
||||
value: props.modelValue.value_type === MarketplaceFeeValueType.FLAT
|
||||
? Number(value) || 0
|
||||
: Number(value),
|
||||
});
|
||||
@ -62,10 +64,10 @@ function updateValue(value: string | number) {
|
||||
<SelectValue placeholder="Pilih dasar" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="transaction">
|
||||
<SelectItem :value="MarketplaceFeeScope.TRANSACTION">
|
||||
Per Transaksi
|
||||
</SelectItem>
|
||||
<SelectItem value="product">
|
||||
<SelectItem :value="MarketplaceFeeScope.PRODUCT">
|
||||
Per Produk
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
@ -84,10 +86,10 @@ function updateValue(value: string | number) {
|
||||
<SelectValue placeholder="Pilih tipe" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="percent">
|
||||
<SelectItem :value="MarketplaceFeeValueType.PERCENT">
|
||||
Persentase (%)
|
||||
</SelectItem>
|
||||
<SelectItem value="flat">
|
||||
<SelectItem :value="MarketplaceFeeValueType.FLAT">
|
||||
Flat (Rp)
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
FieldGroup,
|
||||
FieldSet
|
||||
} from '@/components/ui/field';
|
||||
import { MarketplacePlatform as MarketplacePlatformConst } from '@/constants/marketplace-platform';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type {
|
||||
@ -24,15 +25,15 @@ const props = defineProps<{
|
||||
data: MarketplaceSettingsData;
|
||||
}>();
|
||||
|
||||
const activePlatform = ref<MarketplacePlatform>('tiktok_shop');
|
||||
const activePlatform = ref<MarketplacePlatform>(MarketplacePlatformConst.TIKTOK_SHOP);
|
||||
|
||||
const platformItems: Array<{
|
||||
key: MarketplacePlatform;
|
||||
label: string;
|
||||
icon: typeof Store;
|
||||
}> = [
|
||||
{ key: 'tiktok_shop', label: 'TikTok Shop', icon: Store },
|
||||
{ key: 'shopee', label: 'Shopee', icon: ShoppingBag },
|
||||
{ key: MarketplacePlatformConst.TIKTOK_SHOP, label: 'TikTok Shop', icon: Store },
|
||||
{ key: MarketplacePlatformConst.SHOPEE, label: 'Shopee', icon: ShoppingBag },
|
||||
];
|
||||
|
||||
const form = useForm({
|
||||
@ -88,7 +89,7 @@ function submit() {
|
||||
</nav>
|
||||
|
||||
<div class="min-w-0 flex-1 space-y-6">
|
||||
<Card v-show="activePlatform === 'tiktok_shop'">
|
||||
<Card v-show="activePlatform === MarketplacePlatformConst.TIKTOK_SHOP">
|
||||
<CardContent>
|
||||
<FieldSet>
|
||||
<FieldGroup class="grid gap-4 sm:grid-cols-2">
|
||||
@ -138,7 +139,7 @@ function submit() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card v-show="activePlatform === 'shopee'">
|
||||
<Card v-show="activePlatform === MarketplacePlatformConst.SHOPEE">
|
||||
<CardContent>
|
||||
<FieldSet>
|
||||
<FieldGroup class="grid gap-4 sm:grid-cols-2">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user