feat: enhance payroll period functionality with role-based visibility and improved data handling
This commit is contained in:
parent
54ad2edaf7
commit
8eb3e565ec
@ -15,7 +15,7 @@ protected function handleAction(callable $action, string $successMessage, string
|
||||
$action();
|
||||
Inertia::flash('toast', ['type' => 'success', 'message' => $successMessage]);
|
||||
|
||||
return to_route($redirectRoute);
|
||||
return to_route($redirectRoute, $parameters);
|
||||
} catch (ValidationException $e) {
|
||||
$firstError = collect($e->errors())->flatten()->first();
|
||||
Inertia::flash('toast', ['type' => 'error', 'message' => $firstError ?? 'Terjadi kesalahan.']);
|
||||
|
||||
@ -17,19 +17,30 @@ class PayrollPeriodService
|
||||
{
|
||||
use HandlesCashTransactions;
|
||||
|
||||
private function canViewAll(): bool
|
||||
{
|
||||
return auth()->user()->hasAnyRole(['developer', 'owner', 'direktur', 'admin-toko']);
|
||||
}
|
||||
|
||||
public function getAll(array $filters = []): Collection
|
||||
{
|
||||
return PayrollPeriod::select(['id', 'year', 'month', 'status', 'closed_at', 'created_at'])
|
||||
->withCount('payrolls')
|
||||
->withSum('payrolls', 'total_amount')
|
||||
->withSum('payrolls', 'bonus_amount')
|
||||
->withSum('payrolls', 'deduction_amount')
|
||||
->withCount(['payrolls as paid_count' => function ($q) {
|
||||
$q->paid();
|
||||
}])
|
||||
->withCount(['payrolls as cancelled_count' => function ($q) {
|
||||
$q->cancelled();
|
||||
}])
|
||||
->when(! $this->canViewAll(), function ($query) {
|
||||
$query->withCount(['payrolls as payrolls_count' => fn ($q) => $q->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id()))])
|
||||
->withSum(['payrolls as payrolls_sum_total_amount' => fn ($q) => $q->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id()))], 'total_amount')
|
||||
->withSum(['payrolls as payrolls_sum_bonus_amount' => fn ($q) => $q->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id()))], 'bonus_amount')
|
||||
->withSum(['payrolls as payrolls_sum_deduction_amount' => fn ($q) => $q->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id()))], 'deduction_amount')
|
||||
->withCount(['payrolls as paid_count' => fn ($q) => $q->paid()->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id()))])
|
||||
->withCount(['payrolls as cancelled_count' => fn ($q) => $q->cancelled()->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id()))]);
|
||||
})
|
||||
->when($this->canViewAll(), function ($query) {
|
||||
$query->withCount('payrolls')
|
||||
->withSum('payrolls', 'total_amount')
|
||||
->withSum('payrolls', 'bonus_amount')
|
||||
->withSum('payrolls', 'deduction_amount')
|
||||
->withCount(['payrolls as paid_count' => fn ($q) => $q->paid()])
|
||||
->withCount(['payrolls as cancelled_count' => fn ($q) => $q->cancelled()]);
|
||||
})
|
||||
->latest('year')
|
||||
->latest('month')
|
||||
->get();
|
||||
@ -39,16 +50,22 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
|
||||
{
|
||||
return PayrollPeriod::query()
|
||||
->select(['id', 'year', 'month', 'status', 'closed_at', 'created_at'])
|
||||
->withCount('payrolls')
|
||||
->withSum('payrolls', 'total_amount')
|
||||
->withSum('payrolls', 'bonus_amount')
|
||||
->withSum('payrolls', 'deduction_amount')
|
||||
->withCount(['payrolls as paid_count' => function ($q) {
|
||||
$q->paid();
|
||||
}])
|
||||
->withCount(['payrolls as cancelled_count' => function ($q) {
|
||||
$q->cancelled();
|
||||
}])
|
||||
->when(! $this->canViewAll(), function ($query) {
|
||||
$query->withCount(['payrolls as payrolls_count' => fn ($q) => $q->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id()))])
|
||||
->withSum(['payrolls as payrolls_sum_total_amount' => fn ($q) => $q->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id()))], 'total_amount')
|
||||
->withSum(['payrolls as payrolls_sum_bonus_amount' => fn ($q) => $q->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id()))], 'bonus_amount')
|
||||
->withSum(['payrolls as payrolls_sum_deduction_amount' => fn ($q) => $q->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id()))], 'deduction_amount')
|
||||
->withCount(['payrolls as paid_count' => fn ($q) => $q->paid()->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id()))])
|
||||
->withCount(['payrolls as cancelled_count' => fn ($q) => $q->cancelled()->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id()))]);
|
||||
})
|
||||
->when($this->canViewAll(), function ($query) {
|
||||
$query->withCount('payrolls')
|
||||
->withSum('payrolls', 'total_amount')
|
||||
->withSum('payrolls', 'bonus_amount')
|
||||
->withSum('payrolls', 'deduction_amount')
|
||||
->withCount(['payrolls as paid_count' => fn ($q) => $q->paid()])
|
||||
->withCount(['payrolls as cancelled_count' => fn ($q) => $q->cancelled()]);
|
||||
})
|
||||
->when($search, fn ($q) => $q->where('year', 'like', "%{$search}%"))
|
||||
->orderBy($sort, $direction)
|
||||
->paginate($perPage);
|
||||
@ -59,6 +76,7 @@ public function getDetail(PayrollPeriod $period): PayrollPeriod
|
||||
return $period->load([
|
||||
'payrolls' => function ($query) {
|
||||
$query->with(['employee.user.userProfile', 'payrollAdjustments'])
|
||||
->when(! $this->canViewAll(), fn ($q) => $q->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id())))
|
||||
->orderBy('id');
|
||||
},
|
||||
]);
|
||||
|
||||
@ -50,14 +50,15 @@ type CreateColumnsParams = {
|
||||
handleClose: (period: PayrollPeriod) => void;
|
||||
handleReopen: (period: PayrollPeriod) => void;
|
||||
can: (permission: string) => boolean;
|
||||
canViewAll: boolean;
|
||||
};
|
||||
|
||||
export function createPayrollPeriodColumns(
|
||||
params: CreateColumnsParams,
|
||||
): ColumnDef<PayrollPeriod>[] {
|
||||
const { showUrl, handleClose, handleReopen, can } = params;
|
||||
const { showUrl, handleClose, handleReopen, can, canViewAll } = params;
|
||||
|
||||
return [
|
||||
const columns: ColumnDef<PayrollPeriod>[] = [
|
||||
{
|
||||
accessorKey: 'year',
|
||||
header: () => <span>Periode</span>,
|
||||
@ -67,7 +68,10 @@ export function createPayrollPeriodColumns(
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
];
|
||||
|
||||
if (canViewAll) {
|
||||
columns.push({
|
||||
accessorKey: 'payrolls_count',
|
||||
header: () => <span>Jumlah Karyawan</span>,
|
||||
cell: ({ row }) => (
|
||||
@ -75,7 +79,10 @@ export function createPayrollPeriodColumns(
|
||||
{row.getValue('payrolls_count') as number}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
columns.push(
|
||||
{
|
||||
accessorKey: 'payrolls_sum_total_amount',
|
||||
header: () => <span>Total Gaji</span>,
|
||||
@ -131,7 +138,10 @@ export function createPayrollPeriodColumns(
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
);
|
||||
|
||||
if (canViewAll) {
|
||||
columns.push({
|
||||
id: 'payment_status',
|
||||
header: () => <span>Status Bayar</span>,
|
||||
cell: ({ row }) => {
|
||||
@ -161,7 +171,10 @@ export function createPayrollPeriodColumns(
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
columns.push(
|
||||
{
|
||||
accessorKey: 'status',
|
||||
header: () => <span>Status</span>,
|
||||
@ -212,5 +225,7 @@ export function createPayrollPeriodColumns(
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
);
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
@ -27,7 +27,8 @@ type Props = {
|
||||
};
|
||||
|
||||
export default function PayrollPeriodIndex({ payrollPeriods }: Props) {
|
||||
const { can } = useCan();
|
||||
const { can, hasAnyRole } = useCan();
|
||||
const canViewAll = hasAnyRole(['developer', 'owner', 'direktur', 'admin-toko']);
|
||||
const [closing, setClosing] = useState<PayrollPeriod | null>(null);
|
||||
const [reopening, setReopening] = useState<PayrollPeriod | null>(null);
|
||||
|
||||
@ -81,6 +82,7 @@ export default function PayrollPeriodIndex({ payrollPeriods }: Props) {
|
||||
handleClose: (period) => setClosing(period),
|
||||
handleReopen: (period) => setReopening(period),
|
||||
can,
|
||||
canViewAll,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@ -81,7 +81,9 @@ export function createPayrollColumns(
|
||||
can,
|
||||
} = params;
|
||||
|
||||
return [
|
||||
const hasAnyPayrollAction = can('payroll.adjust') || can('payroll.pay') || can('payroll.cancel');
|
||||
|
||||
const columns: ColumnDef<Payroll>[] = [
|
||||
{
|
||||
id: 'employee_name',
|
||||
header: () => <span>Nama Karyawan</span>,
|
||||
@ -185,7 +187,7 @@ export function createPayrollColumns(
|
||||
<span className="max-w-[100px] text-muted-foreground">
|
||||
{adj.description}
|
||||
</span>
|
||||
{payroll.status === 'unpaid' && (
|
||||
{can('payroll.adjust') && payroll.status === 'unpaid' && (
|
||||
<button
|
||||
onClick={() =>
|
||||
handleDeleteAdjustment(
|
||||
@ -204,7 +206,10 @@ export function createPayrollColumns(
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
];
|
||||
|
||||
if (hasAnyPayrollAction) {
|
||||
columns.push({
|
||||
id: 'actions',
|
||||
header: () => <span className="block text-center">Aksi</span>,
|
||||
meta: {
|
||||
@ -249,6 +254,8 @@ export function createPayrollColumns(
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
@ -40,7 +40,8 @@ type Props = {
|
||||
};
|
||||
|
||||
export default function PayrollPeriodShow({ payrollPeriod }: Props) {
|
||||
const { can } = useCan();
|
||||
const { can, hasAnyRole } = useCan();
|
||||
const canViewAll = hasAnyRole(['developer', 'owner', 'direktur', 'admin-toko']);
|
||||
const [paying, setPaying] = useState<Payroll | null>(null);
|
||||
const [cancelling, setCancelling] = useState<Payroll | null>(null);
|
||||
const [addingAdjustment, setAddingAdjustment] = useState<Payroll | null>(
|
||||
@ -103,19 +104,23 @@ export default function PayrollPeriodShow({ payrollPeriod }: Props) {
|
||||
can,
|
||||
});
|
||||
|
||||
const totalBaseSalary = payrollPeriod.payrolls.reduce(
|
||||
const activePayrolls = payrollPeriod.payrolls.filter(
|
||||
(p) => p.status !== 'cancelled',
|
||||
);
|
||||
|
||||
const totalBaseSalary = activePayrolls.reduce(
|
||||
(sum, p) => sum + p.base_salary,
|
||||
0,
|
||||
);
|
||||
const totalBonus = payrollPeriod.payrolls.reduce(
|
||||
const totalBonus = activePayrolls.reduce(
|
||||
(sum, p) => sum + p.bonus_amount,
|
||||
0,
|
||||
);
|
||||
const totalDeduction = payrollPeriod.payrolls.reduce(
|
||||
const totalDeduction = activePayrolls.reduce(
|
||||
(sum, p) => sum + p.deduction_amount,
|
||||
0,
|
||||
);
|
||||
const totalAmount = payrollPeriod.payrolls.reduce(
|
||||
const totalAmount = activePayrolls.reduce(
|
||||
(sum, p) => sum + p.total_amount,
|
||||
0,
|
||||
);
|
||||
@ -133,13 +138,15 @@ export default function PayrollPeriodShow({ payrollPeriod }: Props) {
|
||||
Gaji {MONTH_NAMES[payrollPeriod.month]}{' '}
|
||||
{payrollPeriod.year}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{payrollPeriod.payrolls.length} karyawan ·
|
||||
Status:{' '}
|
||||
{payrollPeriod.status === 'open'
|
||||
? 'Terbuka'
|
||||
: 'Ditutup'}
|
||||
</p>
|
||||
{canViewAll && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{payrollPeriod.payrolls.length} karyawan ·
|
||||
Status:{' '}
|
||||
{payrollPeriod.status === 'open'
|
||||
? 'Terbuka'
|
||||
: 'Ditutup'}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Button asChild variant="outline">
|
||||
<Link href={payrollPeriodsIndex.url()}>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user