feat: update EmployeeAdvance status values and related logic, modify notifications accordingly
This commit is contained in:
parent
7d21a1e6db
commit
b905c0a2c6
@ -288,7 +288,7 @@ ## Enums
|
|||||||
|------|--------|---------|
|
|------|--------|---------|
|
||||||
| `CashTransactionType` | deposit, expense, transfer, withdrawal | cash_transactions.type |
|
| `CashTransactionType` | deposit, expense, transfer, withdrawal | cash_transactions.type |
|
||||||
| `CuttingStatus` | in_progress, completed, cancelled | cuttings.status |
|
| `CuttingStatus` | in_progress, completed, cancelled | cuttings.status |
|
||||||
| `EmployeeAdvanceStatus` | pending, approved, rejected, paid, cancelled | employee_advances.status |
|
| `EmployeeAdvanceStatus` | pending, disbursed, partial, repaid, rejected | employee_advances.status |
|
||||||
| `EmploymentStatus` | full_time, part_time, contract, internship, resigned | employees.employment_status |
|
| `EmploymentStatus` | full_time, part_time, contract, internship, resigned | employees.employment_status |
|
||||||
| `Gender` | male, female | user_profiles.gender |
|
| `Gender` | male, female | user_profiles.gender |
|
||||||
| `LeaveRequestStatus` | pending, approved, rejected, cancelled | leave_requests.status |
|
| `LeaveRequestStatus` | pending, approved, rejected, cancelled | leave_requests.status |
|
||||||
|
|||||||
@ -8,20 +8,20 @@ enum EmployeeAdvanceStatus: string
|
|||||||
{
|
{
|
||||||
use HasValues;
|
use HasValues;
|
||||||
|
|
||||||
case APPROVED = 'approved';
|
case DISBURSED = 'disbursed';
|
||||||
case CANCELLED = 'cancelled';
|
case PARTIAL = 'partial';
|
||||||
case PAID = 'paid';
|
|
||||||
case PENDING = 'pending';
|
case PENDING = 'pending';
|
||||||
case REJECTED = 'rejected';
|
case REJECTED = 'rejected';
|
||||||
|
case REPAID = 'repaid';
|
||||||
|
|
||||||
public function label(): string
|
public function label(): string
|
||||||
{
|
{
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
self::APPROVED => 'Disetujui',
|
self::DISBURSED => 'Dikeluarkan',
|
||||||
self::CANCELLED => 'Dibatalkan',
|
self::PARTIAL => 'Dicicil',
|
||||||
self::PAID => 'Dibayar',
|
|
||||||
self::PENDING => 'Menunggu',
|
self::PENDING => 'Menunggu',
|
||||||
self::REJECTED => 'Ditolak',
|
self::REJECTED => 'Ditolak',
|
||||||
|
self::REPAID => 'Lunas',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,21 +75,21 @@ protected function statusLabel(): Attribute
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
#[Scope]
|
||||||
protected function approved(Builder $query): void
|
protected function disbursed(Builder $query): void
|
||||||
{
|
{
|
||||||
$query->where('status', EmployeeAdvanceStatus::APPROVED);
|
$query->where('status', EmployeeAdvanceStatus::DISBURSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
#[Scope]
|
||||||
protected function cancelled(Builder $query): void
|
protected function partial(Builder $query): void
|
||||||
{
|
{
|
||||||
$query->where('status', EmployeeAdvanceStatus::CANCELLED);
|
$query->where('status', EmployeeAdvanceStatus::PARTIAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
#[Scope]
|
||||||
protected function paid(Builder $query): void
|
protected function repaid(Builder $query): void
|
||||||
{
|
{
|
||||||
$query->where('status', EmployeeAdvanceStatus::PAID);
|
$query->where('status', EmployeeAdvanceStatus::REPAID);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Scope]
|
#[Scope]
|
||||||
|
|||||||
@ -63,13 +63,13 @@ public function store(array $data): EmployeeAdvance
|
|||||||
|
|
||||||
public function update(EmployeeAdvance $employeeAdvance, array $data): EmployeeAdvance
|
public function update(EmployeeAdvance $employeeAdvance, array $data): EmployeeAdvance
|
||||||
{
|
{
|
||||||
if ($employeeAdvance->status === EmployeeAdvanceStatus::PAID) {
|
if ($employeeAdvance->status === EmployeeAdvanceStatus::REPAID) {
|
||||||
throw ValidationException::withMessages([
|
throw ValidationException::withMessages([
|
||||||
'amount' => 'Kasbon yang sudah dibayar tidak dapat diedit.',
|
'amount' => 'Kasbon yang sudah dikembalikan tidak dapat diedit.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($employeeAdvance->status === EmployeeAdvanceStatus::APPROVED && $employeeAdvance->cash_transaction_id) {
|
if ($employeeAdvance->status === EmployeeAdvanceStatus::DISBURSED && $employeeAdvance->cash_transaction_id) {
|
||||||
$oldAmount = $employeeAdvance->amount;
|
$oldAmount = $employeeAdvance->amount;
|
||||||
$newAmount = $data['amount'];
|
$newAmount = $data['amount'];
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ public function update(EmployeeAdvance $employeeAdvance, array $data): EmployeeA
|
|||||||
public function destroy(EmployeeAdvance $employeeAdvance): bool
|
public function destroy(EmployeeAdvance $employeeAdvance): bool
|
||||||
{
|
{
|
||||||
return DB::transaction(function () use ($employeeAdvance) {
|
return DB::transaction(function () use ($employeeAdvance) {
|
||||||
if ($employeeAdvance->status === EmployeeAdvanceStatus::APPROVED && $employeeAdvance->cash_transaction_id) {
|
if ($employeeAdvance->status === EmployeeAdvanceStatus::DISBURSED && $employeeAdvance->cash_transaction_id) {
|
||||||
$this->creditCash(
|
$this->creditCash(
|
||||||
$employeeAdvance->amount,
|
$employeeAdvance->amount,
|
||||||
'Pembatalan kasbon: '.$employeeAdvance->description,
|
'Pembatalan kasbon: '.$employeeAdvance->description,
|
||||||
@ -122,7 +122,7 @@ public function destroy(EmployeeAdvance $employeeAdvance): bool
|
|||||||
$employeeAdvance->cashTransaction()->delete();
|
$employeeAdvance->cashTransaction()->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($employeeAdvance->status === EmployeeAdvanceStatus::PAID) {
|
if ($employeeAdvance->status === EmployeeAdvanceStatus::REPAID || $employeeAdvance->status === EmployeeAdvanceStatus::PARTIAL) {
|
||||||
$this->creditCash(
|
$this->creditCash(
|
||||||
$employeeAdvance->amount,
|
$employeeAdvance->amount,
|
||||||
'Pembatalan kasbon: '.$employeeAdvance->description,
|
'Pembatalan kasbon: '.$employeeAdvance->description,
|
||||||
@ -159,7 +159,7 @@ public function approve(EmployeeAdvance $employeeAdvance): EmployeeAdvance
|
|||||||
|
|
||||||
$employeeAdvance->update([
|
$employeeAdvance->update([
|
||||||
'cash_transaction_id' => $cashTransaction->id,
|
'cash_transaction_id' => $cashTransaction->id,
|
||||||
'status' => EmployeeAdvanceStatus::APPROVED,
|
'status' => EmployeeAdvanceStatus::DISBURSED,
|
||||||
'verified_by_id' => auth()->id(),
|
'verified_by_id' => auth()->id(),
|
||||||
'verified_at' => now(),
|
'verified_at' => now(),
|
||||||
]);
|
]);
|
||||||
@ -169,8 +169,8 @@ public function approve(EmployeeAdvance $employeeAdvance): EmployeeAdvance
|
|||||||
|
|
||||||
NotificationService::notify(
|
NotificationService::notify(
|
||||||
roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
|
roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
|
||||||
title: 'Kasbon Disetujui',
|
title: 'Kasbon Dikeluarkan',
|
||||||
body: 'Kasbon sebesar Rp '.number_format($employeeAdvance->amount, 0, ',', '.').' telah disetujui'.' oleh '.auth()->user()->full_name.'.',
|
body: 'Kasbon sebesar Rp '.number_format($employeeAdvance->amount, 0, ',', '.').' telah disetujui dan dikeluarkan'.' oleh '.auth()->user()->full_name.'.',
|
||||||
url: route('admin.finance.employee-advances.index'),
|
url: route('admin.finance.employee-advances.index'),
|
||||||
additionalUser: $employeeAdvance->employee->user ?? null,
|
additionalUser: $employeeAdvance->employee->user ?? null,
|
||||||
);
|
);
|
||||||
@ -208,7 +208,7 @@ public function pay(EmployeeAdvance $employeeAdvance, int $amount): EmployeeAdva
|
|||||||
|
|
||||||
$employeeAdvance->update([
|
$employeeAdvance->update([
|
||||||
'paid_amount' => $newPaidAmount,
|
'paid_amount' => $newPaidAmount,
|
||||||
'status' => $isFullyPaid ? EmployeeAdvanceStatus::PAID : $employeeAdvance->status,
|
'status' => $isFullyPaid ? EmployeeAdvanceStatus::REPAID : EmployeeAdvanceStatus::PARTIAL,
|
||||||
'paid_by_id' => $isFullyPaid ? auth()->id() : $employeeAdvance->paid_by_id,
|
'paid_by_id' => $isFullyPaid ? auth()->id() : $employeeAdvance->paid_by_id,
|
||||||
'paid_at' => $isFullyPaid ? now() : $employeeAdvance->paid_at,
|
'paid_at' => $isFullyPaid ? now() : $employeeAdvance->paid_at,
|
||||||
]);
|
]);
|
||||||
@ -216,13 +216,13 @@ public function pay(EmployeeAdvance $employeeAdvance, int $amount): EmployeeAdva
|
|||||||
return $employeeAdvance;
|
return $employeeAdvance;
|
||||||
});
|
});
|
||||||
|
|
||||||
$notificationBody = $employeeAdvance->status === EmployeeAdvanceStatus::PAID
|
$notificationBody = $employeeAdvance->status === EmployeeAdvanceStatus::REPAID
|
||||||
? 'Kasbon sebesar Rp '.number_format($employeeAdvance->amount, 0, ',', '.').' telah dibayar lunas'.' oleh '.auth()->user()->full_name.'.'
|
? 'Kasbon sebesar Rp '.number_format($employeeAdvance->amount, 0, ',', '.').' telah dikembalikan lunas'.' oleh '.auth()->user()->full_name.'.'
|
||||||
: 'Pembayaran kasbon sebesar Rp '.number_format($amount, 0, ',', '.').' oleh '.auth()->user()->full_name.'. Sisa: Rp '.number_format($employeeAdvance->amount - $employeeAdvance->paid_amount, 0, ',', '.').'.';
|
: 'Pembayaran kasbon sebesar Rp '.number_format($amount, 0, ',', '.').' oleh '.auth()->user()->full_name.'. Sisa: Rp '.number_format($employeeAdvance->amount - $employeeAdvance->paid_amount, 0, ',', '.').'.';
|
||||||
|
|
||||||
NotificationService::notify(
|
NotificationService::notify(
|
||||||
roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
|
roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
|
||||||
title: $employeeAdvance->status === EmployeeAdvanceStatus::PAID ? 'Kasbon Dibayar' : 'Pembayaran Kasbon',
|
title: $employeeAdvance->status === EmployeeAdvanceStatus::REPAID ? 'Kasbon Dikembalikan' : 'Pembayaran Kasbon',
|
||||||
body: $notificationBody,
|
body: $notificationBody,
|
||||||
url: route('admin.finance.employee-advances.index'),
|
url: route('admin.finance.employee-advances.index'),
|
||||||
additionalUser: $employeeAdvance->employee->user ?? null,
|
additionalUser: $employeeAdvance->employee->user ?? null,
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use App\Enums\CashTransactionType;
|
use App\Enums\CashTransactionType;
|
||||||
|
use App\Enums\EmployeeAdvanceStatus;
|
||||||
use App\Enums\OrderChannel;
|
use App\Enums\OrderChannel;
|
||||||
use App\Enums\OrderStatus;
|
use App\Enums\OrderStatus;
|
||||||
use App\Enums\PaymentType;
|
use App\Enums\PaymentType;
|
||||||
@ -286,7 +287,7 @@ public function getExpenseSummary(?string $startDate, ?string $endDate): array
|
|||||||
$expenseQuery = Expense::query();
|
$expenseQuery = Expense::query();
|
||||||
$this->applyDateFilter($expenseQuery, $startDate, $endDate, 'expenses.created_at');
|
$this->applyDateFilter($expenseQuery, $startDate, $endDate, 'expenses.created_at');
|
||||||
|
|
||||||
$advanceQuery = EmployeeAdvance::where('status', 'paid');
|
$advanceQuery = EmployeeAdvance::where('status', EmployeeAdvanceStatus::REPAID);
|
||||||
$this->applyDateFilter($advanceQuery, $startDate, $endDate, 'employee_advances.created_at');
|
$this->applyDateFilter($advanceQuery, $startDate, $endDate, 'employee_advances.created_at');
|
||||||
|
|
||||||
$purchaseQuery = Purchase::query();
|
$purchaseQuery = Purchase::query();
|
||||||
@ -326,7 +327,7 @@ public function getMonthlyExpense(?string $startDate, ?string $endDate): array
|
|||||||
->get()
|
->get()
|
||||||
->keyBy('month');
|
->keyBy('month');
|
||||||
|
|
||||||
$advanceMonthly = EmployeeAdvance::where('status', 'paid');
|
$advanceMonthly = EmployeeAdvance::where('status', EmployeeAdvanceStatus::REPAID);
|
||||||
$this->applyDateFilter($advanceMonthly, $startDate, $endDate, 'employee_advances.created_at');
|
$this->applyDateFilter($advanceMonthly, $startDate, $endDate, 'employee_advances.created_at');
|
||||||
|
|
||||||
$advanceByMonth = (clone $advanceMonthly)->toBase()
|
$advanceByMonth = (clone $advanceMonthly)->toBase()
|
||||||
|
|||||||
@ -113,7 +113,7 @@ public function getExpenseSummary(): array
|
|||||||
->first();
|
->first();
|
||||||
|
|
||||||
$advanceTotal = EmployeeAdvance::whereDate('created_at', $today)
|
$advanceTotal = EmployeeAdvance::whereDate('created_at', $today)
|
||||||
->approved()
|
->disbursed()
|
||||||
->selectRaw('COALESCE(SUM(amount), 0) as total')
|
->selectRaw('COALESCE(SUM(amount), 0) as total')
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ public function definition(): array
|
|||||||
'paid_amount' => 0,
|
'paid_amount' => 0,
|
||||||
'description' => fake()->sentence(),
|
'description' => fake()->sentence(),
|
||||||
'due_date' => fake()->date(),
|
'due_date' => fake()->date(),
|
||||||
'status' => fake()->randomElement(['pending', 'approved', 'paid', 'rejected', 'cancelled']),
|
'status' => fake()->randomElement(['pending', 'disbursed', 'partial', 'repaid', 'rejected']),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export type EmployeeAdvance = {
|
|||||||
description: string;
|
description: string;
|
||||||
due_date: string;
|
due_date: string;
|
||||||
formatted_due_date: string;
|
formatted_due_date: string;
|
||||||
status: 'pending' | 'approved' | 'paid' | 'rejected' | 'cancelled';
|
status: 'pending' | 'disbursed' | 'partial' | 'repaid' | 'rejected';
|
||||||
created_at: string;
|
created_at: string;
|
||||||
formatted_created_at: string;
|
formatted_created_at: string;
|
||||||
employee: {
|
employee: {
|
||||||
@ -48,22 +48,22 @@ function getStatusBadge(status: string) {
|
|||||||
label: 'Menunggu',
|
label: 'Menunggu',
|
||||||
className: 'bg-yellow-100 text-yellow-800 hover:bg-yellow-100',
|
className: 'bg-yellow-100 text-yellow-800 hover:bg-yellow-100',
|
||||||
},
|
},
|
||||||
approved: {
|
disbursed: {
|
||||||
label: 'Disetujui',
|
label: 'Dikeluarkan',
|
||||||
className: 'bg-green-100 text-green-800 hover:bg-green-100',
|
className: 'bg-green-100 text-green-800 hover:bg-green-100',
|
||||||
},
|
},
|
||||||
paid: {
|
partial: {
|
||||||
label: 'Dibayar',
|
label: 'Cicilan',
|
||||||
|
className: 'bg-orange-100 text-orange-800 hover:bg-orange-100',
|
||||||
|
},
|
||||||
|
repaid: {
|
||||||
|
label: 'Lunas',
|
||||||
className: 'bg-blue-100 text-blue-800 hover:bg-blue-100',
|
className: 'bg-blue-100 text-blue-800 hover:bg-blue-100',
|
||||||
},
|
},
|
||||||
rejected: {
|
rejected: {
|
||||||
label: 'Ditolak',
|
label: 'Ditolak',
|
||||||
className: 'bg-red-100 text-red-800 hover:bg-red-100',
|
className: 'bg-red-100 text-red-800 hover:bg-red-100',
|
||||||
},
|
},
|
||||||
cancelled: {
|
|
||||||
label: 'Dibatalkan',
|
|
||||||
className: 'bg-gray-100 text-gray-800 hover:bg-gray-100',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = statusConfig[status] ?? statusConfig.pending;
|
const config = statusConfig[status] ?? statusConfig.pending;
|
||||||
@ -127,7 +127,7 @@ export function createEmployeeAdvanceColumns(
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const employeeAdvance = row.original;
|
const employeeAdvance = row.original;
|
||||||
|
|
||||||
if (employeeAdvance.status === 'paid') {
|
if (employeeAdvance.status === 'repaid') {
|
||||||
return <span className="text-green-600">Lunas</span>;
|
return <span className="text-green-600">Lunas</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,7 +202,7 @@ export function createEmployeeAdvanceColumns(
|
|||||||
),
|
),
|
||||||
show:
|
show:
|
||||||
can('employee_advances.pay') &&
|
can('employee_advances.pay') &&
|
||||||
employeeAdvance.status === 'approved',
|
(employeeAdvance.status === 'disbursed' || employeeAdvance.status === 'partial'),
|
||||||
onClick: () => handlePay(employeeAdvance),
|
onClick: () => handlePay(employeeAdvance),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -522,7 +522,7 @@ function notifEmployeeWithRole(string $role = 'Kasir'): array
|
|||||||
Notification::assertSentTo($developer, WebPushNotification::class);
|
Notification::assertSentTo($developer, WebPushNotification::class);
|
||||||
Notification::assertSentTo($kasbonUser, WebPushNotification::class);
|
Notification::assertSentTo($kasbonUser, WebPushNotification::class);
|
||||||
|
|
||||||
expect($kasbonUser->notifications()->where('title', 'Kasbon Disetujui')->count())->toBe(1);
|
expect($kasbonUser->notifications()->where('title', 'Kasbon Dikeluarkan')->count())->toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('EmployeeAdvanceService pay sends notification including the employee', function () {
|
test('EmployeeAdvanceService pay sends notification including the employee', function () {
|
||||||
@ -555,7 +555,7 @@ function notifEmployeeWithRole(string $role = 'Kasir'): array
|
|||||||
Notification::assertSentTo($developer, WebPushNotification::class);
|
Notification::assertSentTo($developer, WebPushNotification::class);
|
||||||
Notification::assertSentTo($kasbonUser, WebPushNotification::class);
|
Notification::assertSentTo($kasbonUser, WebPushNotification::class);
|
||||||
|
|
||||||
expect($kasbonUser->notifications()->where('title', 'Kasbon Dibayar')->count())->toBe(1);
|
expect($kasbonUser->notifications()->where('title', 'Kasbon Dikembalikan')->count())->toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user