PHPackages                             aliziodev/laravel-karyawan-core - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. aliziodev/laravel-karyawan-core

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

aliziodev/laravel-karyawan-core
===============================

Fondasi data karyawan reusable untuk aplikasi bisnis Laravel Indonesia

v1.2.1(1mo ago)03MITPHPPHP ^8.2|^8.3|^8.4CI passing

Since Apr 14Pushed 1mo agoCompare

[ Source](https://github.com/aliziodev/laravel-karyawan-core)[ Packagist](https://packagist.org/packages/aliziodev/laravel-karyawan-core)[ RSS](/packages/aliziodev-laravel-karyawan-core/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (4)Dependencies (11)Versions (5)Used By (0)

laravel-karyawan-core
=====================

[](#laravel-karyawan-core)

**Fondasi data karyawan yang dapat digunakan ulang untuk aplikasi bisnis Laravel Indonesia.**

Package Laravel siap produksi yang menyediakan fondasi lengkap manajemen data karyawan — mulai dari struktur organisasi, profil karyawan, manajemen dokumen, kontak darurat, riwayat perubahan, tautan akun pengguna, REST API penuh, hingga export data karyawan ke XLSX — semuanya dapat dikonfigurasi dan dipublikasikan ke aplikasi Anda.

[![PHP Version](https://camo.githubusercontent.com/c9f64f714c636ba27a3bba6dfd52f98426832db1262747efa54b212d16943651/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253545382e322d626c7565)](https://www.php.net/)[![Laravel](https://camo.githubusercontent.com/fbcc00bb6481a4fd959f225b19c62a0af617cb5b95918a44a66e8b66f424043e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c61726176656c2d25354531312e3025374325354531322e302d726564)](https://laravel.com)[![Latest Version on Packagist](https://camo.githubusercontent.com/6859506b09b258f1f91713b95928729f56ab400b4ed382eb2375b149b5ad36b1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f616c697a696f6465762f6c61726176656c2d6b6172796177616e2d636f72652e737667)](https://packagist.org/packages/aliziodev/laravel-karyawan-core)[![Total Downloads](https://camo.githubusercontent.com/05f8c5dccc863e79d95110894f5c4c23f8391da64eeeca179652f3d38bafb94a/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f616c697a696f6465762f6c61726176656c2d6b6172796177616e2d636f72652e737667)](https://packagist.org/packages/aliziodev/laravel-karyawan-core)[![License: MIT](https://camo.githubusercontent.com/784362b26e4b3546254f1893e778ba64616e362bd6ac791991d2c9e880a3a64e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e2e737667)](LICENSE)

---

Daftar Isi
----------

[](#daftar-isi)

- [Fitur](#fitur)
- [Persyaratan](#persyaratan)
- [Instalasi](#instalasi)
- [Konfigurasi](#konfigurasi)
    - [Variabel Environment](#variabel-environment)
    - [File Konfigurasi](#file-konfigurasi)
- [Database](#database)
    - [Migrasi](#migrasi)
    - [Prefix Tabel](#prefix-tabel)
    - [Kustomisasi Nama Tabel](#kustomisasi-nama-tabel)
- [Penggunaan](#penggunaan)
    - [Perintah Instalasi Interaktif](#perintah-instalasi-interaktif)
    - [Model](#model)
    - [Actions](#actions)
    - [Services](#services)
    - [Events](#events)
    - [Data Transfer Objects (DTO)](#data-transfer-objects-dto)
- [REST API](#rest-api)
    - [Mengaktifkan Route API](#mengaktifkan-route-api)
    - [Daftar Endpoint API](#daftar-endpoint-api)
        - [Export Karyawan (XLSX)](#export-karyawan-xlsx)
    - [Format Respons API](#format-respons-api)
- [Antarmuka Web](#antarmuka-web)
    - [Inertia.js](#inertiajs)
    - [Blade](#blade)
    - [Nama Route Web](#nama-route-web)
- [Publish Controllers](#publish-controllers)
- [Generator Kode Karyawan](#generator-kode-karyawan)
- [Otorisasi (Policies)](#otorisasi-policies)
- [Referensi Enum](#referensi-enum)
- [Penggunaan Lanjutan](#penggunaan-lanjutan)
    - [Mendengarkan Events](#mendengarkan-events)
    - [Mengganti Generator Kode Karyawan](#mengganti-generator-kode-karyawan)
    - [Mengganti Policies](#mengganti-policies)
    - [Memperluas Model](#memperluas-model)
    - [Menggunakan Model User yang Berbeda](#menggunakan-model-user-yang-berbeda)
    - [Menangani Exception](#menangani-exception)
- [Pengujian](#pengujian)
- [Lisensi](#lisensi)

---

Fitur
-----

[](#fitur)

- **Struktur Organisasi** — Manajemen Perusahaan, Cabang, Departemen, dan Jabatan lengkap dengan CRUD
- **Manajemen Karyawan** — Profil karyawan lengkap meliputi data pribadi, data ketenagakerjaan, dan identitas
- **Auto-Generate Kode Karyawan** — Prefix dan panjang angka dapat dikonfigurasi, aman dari race condition
- **Pelacakan Status** — Aktif, Tidak Aktif, Mengundurkan Diri, PHK, Pensiun, Cuti Panjang beserta riwayatnya
- **Manajemen Dokumen** — Upload file beserta metadata (tipe, nomor dokumen, masa berlaku), verifikasi checksum
- **Kontak Darurat** — Beberapa kontak per karyawan dengan penanda kontak utama
- **Log Riwayat** — Jejak audit otomatis untuk setiap perubahan status dan tautan akun
- **Tautan Akun Pengguna** — Hubungkan/putuskan model User mana pun ke karyawan, aman dari concurrency
- **REST API** — JSON API lengkap dengan dukungan Laravel Sanctum, respons terpaginasi, dan JSON Resources
- **Export XLSX Karyawan** — Export data karyawan ke file `.xlsx` dari endpoint API maupun route web (Inertia/Blade) dengan filter yang konsisten
- **Controller Web** — Tersedia dalam versi **Inertia.js** maupun **Blade**
- **Events** — Sistem event lengkap untuk setiap siklus hidup karyawan
- **Policies** — Otorisasi berbasis Policy yang dapat diperluas untuk semua model
- **Perintah Instalasi Artisan** — Wizard interaktif `php artisan karyawan:install`

---

Persyaratan
-----------

[](#persyaratan)

DependensiVersiPHP^8.2 | ^8.3 | ^8.4Laravel^11.0 | ^12.0illuminate/support^11.0 | ^12.0> **Opsional:** `inertiajs/inertia-laravel` hanya diperlukan apabila Anda menggunakan controller web versi Inertia.

---

Instalasi
---------

[](#instalasi)

Pasang package melalui Composer:

```
composer require aliziodev/laravel-karyawan-core
```

Package ini menggunakan auto-discovery Laravel. Service provider terdaftar secara otomatis.

---

Konfigurasi
-----------

[](#konfigurasi)

### Perintah Instalasi Interaktif

[](#perintah-instalasi-interaktif)

Cara tercepat untuk menyiapkan package adalah melalui wizard instalasi interaktif:

```
php artisan karyawan:install
```

Wizard akan memandu Anda melalui langkah-langkah berikut:

1. **Publish config** — menyalin `config/karyawan.php` ke aplikasi Anda
2. **Prefix kode karyawan** — misalnya `EMP` → menghasilkan `EMP00001`, `EMP00002`, …
3. **Panjang digit kode** — jumlah angka setelah prefix (default: `5`)
4. **Publish migrasi** — menyalin semua file migrasi ke `database/migrations`
5. **Prefix tabel** — prefix opsional untuk semua tabel (contoh: `hr_` → `hr_employees`)
6. **Publish controllers** — pilih API, Web Inertia, Web Blade, atau semua
7. **Mengaktifkan route** — toggle route API dan/atau Web bawaan
8. **Menjalankan migrasi** — opsional menjalankan `php artisan migrate` langsung

### Variabel Environment

[](#variabel-environment)

Tambahkan variabel berikut ke file `.env` Anda sesuai kebutuhan:

```
# Generate kode karyawan
KARYAWAN_CODE_PREFIX=EMP
KARYAWAN_CODE_PAD_LENGTH=5

# Prefix tabel (opsional, dibiarkan kosong jika tidak diperlukan)
KARYAWAN_TABLE_PREFIX=

# Model User (default: App\Models\User)
KARYAWAN_USER_MODEL=App\Models\User

# Route API
KARYAWAN_ROUTES_API_ENABLED=false
KARYAWAN_ROUTES_API_PREFIX=api/karyawan

# Route Web
KARYAWAN_ROUTES_WEB_ENABLED=false
KARYAWAN_ROUTES_WEB_TYPE=inertia
KARYAWAN_ROUTES_WEB_PREFIX=karyawan
```

### File Konfigurasi

[](#file-konfigurasi)

Publish file konfigurasi secara manual jika diperlukan:

```
php artisan vendor:publish --tag=karyawan-config
```

Referensi konfigurasi lengkap (`config/karyawan.php`):

```
return [

    'employee_code' => [
        'prefix'        => env('KARYAWAN_CODE_PREFIX', 'EMP'),
        'pad_length'    => (int) env('KARYAWAN_CODE_PAD_LENGTH', 5),
        'auto_generate' => true,
    ],

    'table_prefix' => env('KARYAWAN_TABLE_PREFIX', ''),

    'table_names' => [
        'employees'                   => 'employees',
        'companies'                   => 'companies',
        'branches'                    => 'branches',
        'departments'                 => 'departments',
        'positions'                   => 'positions',
        'employee_documents'          => 'employee_documents',
        'employee_emergency_contacts' => 'employee_emergency_contacts',
        'employee_histories'          => 'employee_histories',
    ],

    'user_model' => env('KARYAWAN_USER_MODEL', 'App\\Models\\User'),

    'routes' => [
        'web' => [
            'enabled'    => env('KARYAWAN_ROUTES_WEB_ENABLED', false),
            'type'       => env('KARYAWAN_ROUTES_WEB_TYPE', 'inertia'), // 'inertia' | 'blade'
            'prefix'     => env('KARYAWAN_ROUTES_WEB_PREFIX', 'karyawan'),
            'middleware' => ['web', 'auth'],
        ],
        'api' => [
            'enabled'    => env('KARYAWAN_ROUTES_API_ENABLED', false),
            'prefix'     => env('KARYAWAN_ROUTES_API_PREFIX', 'api/karyawan'),
            'middleware' => ['api', 'auth:sanctum'],
        ],
    ],

];
```

---

Database
--------

[](#database)

### Migrasi

[](#migrasi)

Publish dan jalankan migrasi:

```
php artisan vendor:publish --tag=karyawan-migrations
php artisan migrate
```

Package ini membuat **8 tabel**:

TabelKeterangan`companies`Data master perusahaan`branches`Kantor cabang per perusahaan`departments`Departemen (dapat di-scope per perusahaan)`positions`Jabatan (dapat di-scope per perusahaan)`employees`Profil karyawan lengkap dengan soft delete`employee_documents`File dokumen beserta metadata`employee_emergency_contacts`Kontak darurat karyawan`employee_histories`Jejak audit untuk setiap perubahan signifikan### Prefix Tabel

[](#prefix-tabel)

Jika aplikasi Anda sudah memiliki nama tabel yang bentrok, atur prefix terlebih dahulu:

```
KARYAWAN_TABLE_PREFIX=hr_
```

Semua 8 tabel akan menggunakan prefix tersebut: `hr_employees`, `hr_companies`, dan seterusnya.

> Prefix diterapkan secara otomatis melalui method `getTable()` pada setiap model. Tidak ada perubahan pada file migrasi — cukup set prefix sebelum menjalankan migrasi.

### Kustomisasi Nama Tabel

[](#kustomisasi-nama-tabel)

Untuk kendali penuh atas nama tabel individual, publish config dan perbarui bagian `table_names`:

```
'table_names' => [
    'employees' => 'staff',
    'companies' => 'organizations',
    // ...
],
```

---

Penggunaan
----------

[](#penggunaan)

### Model

[](#model)

Semua model dapat diakses langsung dari namespace package:

```
use Aliziodev\LaravelKaryawanCore\Models\Company;
use Aliziodev\LaravelKaryawanCore\Models\Branch;
use Aliziodev\LaravelKaryawanCore\Models\Department;
use Aliziodev\LaravelKaryawanCore\Models\Position;
use Aliziodev\LaravelKaryawanCore\Models\Employee;
use Aliziodev\LaravelKaryawanCore\Models\EmployeeDocument;
use Aliziodev\LaravelKaryawanCore\Models\EmployeeEmergencyContact;
use Aliziodev\LaravelKaryawanCore\Models\EmployeeHistory;
```

#### Company

[](#company)

```
// Membuat perusahaan baru
$company = Company::create([
    'code'      => 'PT001',
    'name'      => 'PT Maju Bersama',
    'email'     => 'info@majubersama.co.id',
    'phone'     => '021-5551234',
    'is_active' => true,
]);

// Mengambil hanya yang aktif
$aktif = Company::active()->get();

// Relasi
$company->branches;     // Koleksi Branch
$company->departments;  // Koleksi Department
$company->positions;    // Koleksi Position
$company->employees;    // Koleksi Employee
```

#### Branch, Department, Position

[](#branch-department-position)

```
// Filter berdasarkan perusahaan
Branch::byCompany($companyId)->active()->get();
Department::byCompany($companyId)->get();
Position::active()->get();
```

#### Employee — Scope

[](#employee--scope)

```
// Karyawan aktif saja
Employee::active()->get();

// Filter berdasarkan organisasi
Employee::byCompany($companyId)->get();
Employee::byDepartment($departmentId)->get();
Employee::byBranch($branchId)->get();
Employee::byPosition($positionId)->get();

// Pencarian berdasarkan nama, kode, atau email
Employee::search('Budi')->get();

// Karyawan yang sudah/belum memiliki akun login
Employee::withLogin()->get();
Employee::withoutLogin()->get();

// Eager load relasi
Employee::with(['company', 'branch', 'department', 'position', 'manager'])->get();

// Termasuk yang sudah dihapus (soft delete)
Employee::withTrashed()->find($id);
```

#### Employee — Relasi

[](#employee--relasi)

```
$employee->company;           // Company
$employee->branch;            // Branch
$employee->department;        // Department
$employee->position;          // Position
$employee->manager;           // Employee (self-referencing — atasan langsung)
$employee->subordinates;      // Koleksi Employee (bawahan)
$employee->documents;         // Koleksi EmployeeDocument
$employee->emergencyContacts; // Koleksi EmployeeEmergencyContact
$employee->histories;         // Koleksi EmployeeHistory (terbaru lebih dahulu)
```

#### Employee — Method Pembantu

[](#employee--method-pembantu)

```
$employee->hasLogin();   // bool — apakah sudah terhubung ke akun user
$employee->isActive();   // bool — active_status === 'active'
$employee->isWorking();  // bool — aktif bekerja atau sedang cuti panjang
```

---

### Actions

[](#actions)

Action mengenkapsulasi satu operasi bisnis. Setiap action terdaftar di container IoC Laravel dan digunakan melalui **constructor injection** di controller atau service.

#### Membuat Karyawan

[](#membuat-karyawan)

```
use Aliziodev\LaravelKaryawanCore\Actions\CreateEmployeeAction;
use Aliziodev\LaravelKaryawanCore\DataTransferObjects\EmployeeData;
use Aliziodev\LaravelKaryawanCore\Enums\EmploymentType;

class EmployeeController extends Controller
{
    public function __construct(
        private readonly CreateEmployeeAction $createAction,
    ) {}

    public function store(CreateEmployeeRequest $request): RedirectResponse
    {
        $employee = $this->createAction->execute(
            EmployeeData::fromRequest($request)
        );

        // Kode karyawan di-generate otomatis: EMP00001
        return redirect()->route('employees.show', $employee);
    }
}
```

#### Memperbarui Karyawan

[](#memperbarui-karyawan)

```
use Aliziodev\LaravelKaryawanCore\Actions\UpdateEmployeeAction;

class EmployeeController extends Controller
{
    public function __construct(
        private readonly UpdateEmployeeAction $updateAction,
    ) {}

    public function update(UpdateEmployeeRequest $request, Employee $employee): RedirectResponse
    {
        // employee_code tidak dapat diubah melalui action ini
        $this->updateAction->execute($employee, EmployeeData::fromRequest($request));

        return redirect()->route('employees.show', $employee);
    }
}
```

> `UpdateEmployeeAction` hanya mendispatch event `EmployeeUpdated` apabila ada atribut yang benar-benar berubah.

#### Mengubah Status Karyawan

[](#mengubah-status-karyawan)

```
use Aliziodev\LaravelKaryawanCore\Actions\ChangeEmployeeStatusAction;
use Aliziodev\LaravelKaryawanCore\Enums\ActiveStatus;

class EmployeeStatusController extends Controller
{
    public function __construct(
        private readonly ChangeEmployeeStatusAction $changeStatusAction,
    ) {}

    public function __invoke(ChangeEmployeeStatusRequest $request, Employee $employee): RedirectResponse
    {
        $this->changeStatusAction->execute(
            employee:      $employee,
            newStatus:     ActiveStatus::from($request->active_status),
            effectiveDate: $request->effective_date,
            notes:         $request->notes,
            createdBy:     $request->user()?->id,
        );

        // Secara otomatis:
        // - Mengisi exit_date untuk status Resigned / Terminated / Retired
        // - Membuat catatan EmployeeHistory
        // - Mendispatch event EmployeeStatusChanged

        return back()->with('success', 'Status karyawan berhasil diubah.');
    }
}
```

#### Menghubungkan / Memutuskan Akun User

[](#menghubungkan--memutuskan-akun-user)

```
use Aliziodev\LaravelKaryawanCore\Actions\LinkEmployeeUserAction;
use Aliziodev\LaravelKaryawanCore\Actions\UnlinkEmployeeUserAction;

class EmployeeUserController extends Controller
{
    public function __construct(
        private readonly LinkEmployeeUserAction   $linkUserAction,
        private readonly UnlinkEmployeeUserAction $unlinkUserAction,
    ) {}

    public function store(LinkEmployeeUserRequest $request, Employee $employee): RedirectResponse
    {
        $this->linkUserAction->execute(
            employee:  $employee,
            userId:    (int) $request->user_id,
            createdBy: $request->user()?->id,
        );

        return back()->with('success', 'Akun login berhasil dikaitkan.');
    }

    public function destroy(Request $request, Employee $employee): RedirectResponse
    {
        $this->unlinkUserAction->execute(
            employee:  $employee,
            createdBy: $request->user()?->id,
        );

        return back()->with('success', 'Akun login berhasil dilepas.');
    }
}
```

Kedua action menggunakan **pessimistic locking** untuk mencegah race condition, dan melempar `EmployeeUserLinkException` apabila operasi tidak valid.

#### Menyimpan / Menghapus Dokumen

[](#menyimpan--menghapus-dokumen)

```
use Aliziodev\LaravelKaryawanCore\Actions\StoreEmployeeDocumentAction;
use Aliziodev\LaravelKaryawanCore\Actions\DeleteEmployeeDocumentAction;

class EmployeeDocumentController extends Controller
{
    public function __construct(
        private readonly StoreEmployeeDocumentAction  $storeDocumentAction,
        private readonly DeleteEmployeeDocumentAction $deleteDocumentAction,
    ) {}

    public function store(StoreDocumentRequest $request, Employee $employee): RedirectResponse
    {
        $this->storeDocumentAction->execute(
            $employee,
            $request->toData($employee) // mengonversi request ke EmployeeDocumentData
        );

        return back()->with('success', 'Dokumen berhasil disimpan.');
    }

    public function destroy(Employee $employee, EmployeeDocument $document): RedirectResponse
    {
        abort_unless($document->employee_id === $employee->id, 404);

        $this->deleteDocumentAction->execute($document);
        // Menghapus record database dan file dari storage secara bersamaan

        return back()->with('success', 'Dokumen berhasil dihapus.');
    }
}
```

---

### Services

[](#services)

`EmployeeService` adalah singleton yang membungkus semua action dalam satu class yang mudah digunakan. Cocok untuk digunakan di luar controller, misalnya di job, command, atau service lain.

```
use Aliziodev\LaravelKaryawanCore\Services\EmployeeService;
use Aliziodev\LaravelKaryawanCore\Enums\ActiveStatus;

class SomeJob implements ShouldQueue
{
    public function __construct(
        private readonly EmployeeService $employeeService,
    ) {}

    public function handle(): void
    {
        $employee = $this->employeeService->create($employeeData);
        $employee = $this->employeeService->update($employee, $employeeData);
        $employee = $this->employeeService->changeStatus($employee, ActiveStatus::Inactive);
        $employee = $this->employeeService->linkUser($employee, $userId);
        $employee = $this->employeeService->unlinkUser($employee);
        $document = $this->employeeService->storeDocument($employee, $documentData);
        $this->employeeService->deleteDocument($document);
    }
}
```

#### EmployeeDocumentService

[](#employeedocumentservice)

Digunakan untuk menangani operasi file pada storage:

```
use Aliziodev\LaravelKaryawanCore\Services\EmployeeDocumentService;

class EmployeeDocumentController extends Controller
{
    public function __construct(
        private readonly EmployeeDocumentService $documentService,
    ) {}

    public function getUrl(Employee $employee, EmployeeDocument $document): JsonResponse
    {
        // URL sementara (untuk S3/cloud) atau URL permanen (untuk disk lokal)
        $url = $this->documentService->getTemporaryUrl($document, minutes: 60);

        return response()->json(['url' => $url]);
    }
}
```

Method yang tersedia:

MethodKeterangan`storeFile($file, $employee, $disk, $folder)`Menyimpan file dan mengembalikan metadata (path, nama, ukuran, checksum)`deleteFile($document)`Menghapus file dari storage`getTemporaryUrl($document, $minutes)`Menghasilkan URL akses file---

### Events

[](#events)

Semua event didispatch **di luar** transaksi database sehingga listener selalu menerima data yang sudah tersimpan.

EventDidispatch OlehProperti`EmployeeCreated``CreateEmployeeAction``$employee``EmployeeUpdated``UpdateEmployeeAction``$employee`, `$changedAttributes``EmployeeStatusChanged``ChangeEmployeeStatusAction``$employee`, `$previousStatus`, `$newStatus``EmployeeLinkedToUser``LinkEmployeeUserAction``$employee`, `$userId``EmployeeUnlinkedFromUser``UnlinkEmployeeUserAction``$employee`, `$previousUserId`---

### Data Transfer Objects (DTO)

[](#data-transfer-objects-dto)

DTO adalah class `readonly` PHP 8.2 yang digunakan untuk meneruskan data ke action.

#### EmployeeData

[](#employeedata)

```
use Aliziodev\LaravelKaryawanCore\DataTransferObjects\EmployeeData;

// Dari array
$data = EmployeeData::fromArray([
    'full_name'       => 'Budi Santoso',
    'company_id'      => 1,
    'employment_type' => 'permanent',
    'join_date'       => '2025-01-01',
]);

// Dari FormRequest
$data = EmployeeData::fromRequest($request);

// Konversi ke array (nilai null tidak disertakan)
$array = $data->toArray();
```

#### EmployeeDocumentData

[](#employeedocumentdata)

```
use Aliziodev\LaravelKaryawanCore\DataTransferObjects\EmployeeDocumentData;

$data = EmployeeDocumentData::fromArray([
    'type'      => 'ktp',
    'name'      => 'KTP Budi Santoso',
    'file_disk' => 'local',
    'file_path' => 'employee-documents/EMP00001/ktp.jpg',
    'file_name' => 'ktp.jpg',
]);
```

---

REST API
--------

[](#rest-api)

### Mengaktifkan Route API

[](#mengaktifkan-route-api)

```
KARYAWAN_ROUTES_API_ENABLED=true
KARYAWAN_ROUTES_API_PREFIX=api/karyawan
```

Middleware default adalah `['api', 'auth:sanctum']`. Untuk menggantinya:

```
// config/karyawan.php
'api' => [
    'enabled'    => true,
    'prefix'     => 'api/hr',
    'middleware' => ['api', 'auth:sanctum', 'throttle:60,1'],
],
```

### Daftar Endpoint API

[](#daftar-endpoint-api)

Semua endpoint menggunakan prefix yang dikonfigurasi (default: `api/karyawan`).

#### Perusahaan

[](#perusahaan)

MethodEndpointKeterangan`GET``/companies`Daftar terpaginasi. Mendukung `?search=`, `?active_only=1``POST``/companies`Buat perusahaan baru`GET``/companies/{id}`Detail perusahaan beserta cabang, departemen, jabatan`PUT``/companies/{id}`Perbarui perusahaan`DELETE``/companies/{id}`Hapus perusahaan#### Cabang

[](#cabang)

MethodEndpointKeterangan`GET``/branches`Daftar terpaginasi. Mendukung `?company_id=`, `?active_only=1``POST``/branches`Buat cabang baru`GET``/branches/{id}`Detail cabang`PUT``/branches/{id}`Perbarui cabang`DELETE``/branches/{id}`Hapus cabang#### Departemen

[](#departemen)

MethodEndpointKeterangan`GET``/departments`Daftar terpaginasi. Mendukung `?company_id=`, `?active_only=1``POST``/departments`Buat departemen baru`GET``/departments/{id}`Detail departemen`PUT``/departments/{id}`Perbarui departemen`DELETE``/departments/{id}`Hapus departemen#### Jabatan

[](#jabatan)

MethodEndpointKeterangan`GET``/positions`Daftar terpaginasi. Mendukung `?company_id=`, `?active_only=1``POST``/positions`Buat jabatan baru`GET``/positions/{id}`Detail jabatan`PUT``/positions/{id}`Perbarui jabatan`DELETE``/positions/{id}`Hapus jabatan#### Karyawan

[](#karyawan)

MethodEndpointKeterangan`GET``/employees`Daftar terpaginasi. Mendukung `?search=`, `?company_id=`, `?department_id=`, `?active_only=1``POST``/employees`Buat karyawan baru`GET``/employees/export`Export data karyawan ke file XLSX. Mendukung filter export`GET``/employees/{id}`Detail karyawan beserta semua relasinya`PUT``/employees/{id}`Perbarui karyawan`DELETE``/employees/{id}`Soft-delete karyawan#### Export Karyawan (XLSX)

[](#export-karyawan-xlsx)

Endpoint export:

- `GET /employees/export`

Parameter filter yang didukung:

- `search`
- `company_id`, `branch_id`, `department_id`, `position_id`
- `active_status`, `employment_type`
- `with_login`, `without_login`
- `join_date_from`, `join_date_to`
- `exit_date_from`, `exit_date_to`
- `created_at_from`, `created_at_to`
- `sort_by` (`employee_code`, `full_name`, `join_date`, `active_status`, `created_at`)
- `sort_direction` (`asc`, `desc`)

#### Sub-Resource Karyawan

[](#sub-resource-karyawan)

Semua endpoint di bawah ini menggunakan prefix `/employees/{employee}`:

MethodEndpointKeterangan`PATCH``/employees/{employee}/status`Ubah status karyawan`POST``/employees/{employee}/user`Kaitkan karyawan ke akun user`DELETE``/employees/{employee}/user`Putuskan kaitan karyawan dari akun user`GET``/employees/{employee}/documents`Daftar dokumen`POST``/employees/{employee}/documents`Unggah dokumen`DELETE``/employees/{employee}/documents/{document}`Hapus dokumen`GET``/employees/{employee}/emergency-contacts`Daftar kontak darurat`POST``/employees/{employee}/emergency-contacts`Tambah kontak darurat`PUT``/employees/{employee}/emergency-contacts/{contact}`Perbarui kontak darurat`DELETE``/employees/{employee}/emergency-contacts/{contact}`Hapus kontak darurat`GET``/employees/{employee}/histories`Riwayat perubahan karyawan### Format Respons API

[](#format-respons-api)

Semua endpoint daftar mengembalikan respons terpaginasi:

```
{
    "data": [
        {
            "id": 1,
            "employee_code": "EMP00001",
            "full_name": "Budi Santoso",
            "active_status": "active",
            "active_status_label": "Aktif",
            "company_id": 1,
            "has_login": false,
            "is_active": true
        }
    ],
    "meta": {
        "total": 50,
        "per_page": 20,
        "current_page": 1,
        "last_page": 3
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": "..."
    }
}
```

Respons resource tunggal:

```
{
    "data": {
        "id": 1,
        "employee_code": "EMP00001",
        "full_name": "Budi Santoso",
        "work_email": "budi@perusahaan.co.id",
        "gender": "male",
        "gender_label": "Laki-laki",
        "employment_type": "permanent",
        "employment_type_label": "Karyawan Tetap",
        "active_status": "active",
        "active_status_label": "Aktif",
        "company": { "id": 1, "name": "PT Maju Bersama" },
        "department": { "id": 2, "name": "Engineering" },
        "position": { "id": 3, "name": "Senior Developer" }
    }
}
```

#### Body Request: Ubah Status

[](#body-request-ubah-status)

```
{
    "active_status": "resigned",
    "effective_date": "2024-12-31",
    "notes": "Karyawan mengundurkan diri atas kemauan sendiri."
}
```

#### Body Request: Kaitkan Akun User

[](#body-request-kaitkan-akun-user)

```
{
    "user_id": 42
}
```

#### Body Request: Buat Karyawan

[](#body-request-buat-karyawan)

```
{
    "full_name": "Rina Firgina",
    "work_email": "siti@perusahaan.co.id",
    "company_id": 1,
    "branch_id": 2,
    "department_id": 3,
    "position_id": 4,
    "employment_type": "permanent",
    "join_date": "2025-01-15",
    "gender": "female",
    "religion": "islam",
    "marital_status": "single"
}
```

---

Antarmuka Web
-------------

[](#antarmuka-web)

### Inertia.js

[](#inertiajs)

Atur `KARYAWAN_ROUTES_WEB_TYPE=inertia` dan `KARYAWAN_ROUTES_WEB_ENABLED=true`.

Controller merender komponen Inertia menggunakan path kebab-case:

ControllerPath Komponen`CompanyController``karyawan/company/index`, `karyawan/company/create`, `karyawan/company/show`, `karyawan/company/edit``BranchController``karyawan/branch/index`, `karyawan/branch/create`, `karyawan/branch/show`, `karyawan/branch/edit``DepartmentController``karyawan/department/index`, …`PositionController``karyawan/position/index`, …`EmployeeController``karyawan/employee/index`, `karyawan/employee/create`, `karyawan/employee/show`, `karyawan/employee/edit``EmployeeDocumentController``karyawan/employee/document/index``EmployeeEmergencyContactController``karyawan/employee/emergency-contact/index`, `karyawan/employee/emergency-contact/create`, `karyawan/employee/emergency-contact/edit``EmployeeHistoryController``karyawan/employee/history/index`Buat komponen Vue/React yang sesuai di frontend Anda pada path-path tersebut.

### Blade

[](#blade)

Atur `KARYAWAN_ROUTES_WEB_TYPE=blade` dan `KARYAWAN_ROUTES_WEB_ENABLED=true`.

Controller merender Blade view menggunakan notasi titik:

ControllerPath View`CompanyController``karyawan.company.index`, `karyawan.company.create`, `karyawan.company.show`, `karyawan.company.edit``BranchController``karyawan.branch.index`, …`DepartmentController``karyawan.department.index`, …`PositionController``karyawan.position.index`, …`EmployeeController``karyawan.employee.index`, `karyawan.employee.create`, `karyawan.employee.show`, `karyawan.employee.edit``EmployeeDocumentController``karyawan.employee.document.index``EmployeeEmergencyContactController``karyawan.employee.emergency-contact.index`, `karyawan.employee.emergency-contact.create`, `karyawan.employee.emergency-contact.edit``EmployeeHistoryController``karyawan.employee.history.index`Buat template Blade yang sesuai di `resources/views/karyawan/`.

### Nama Route Web

[](#nama-route-web)

Semua route web menggunakan nama yang sama tanpa memandang tipe Inertia atau Blade:

```
karyawan.companies.index / create / store / show / edit / update / destroy
karyawan.branches.index / create / store / show / edit / update / destroy
karyawan.departments.index / create / store / show / edit / update / destroy
karyawan.positions.index / create / store / show / edit / update / destroy
karyawan.employees.index / create / store / show / edit / update / destroy
karyawan.employees.export                    (GET)

karyawan.employees.status                    (PATCH)
karyawan.employees.user.store                (POST)
karyawan.employees.user.destroy              (DELETE)
karyawan.employees.documents.index           (GET)
karyawan.employees.documents.store           (POST)
karyawan.employees.documents.destroy         (DELETE)
karyawan.employees.emergency-contacts.index  (GET)
karyawan.employees.emergency-contacts.create (GET)
karyawan.employees.emergency-contacts.store  (POST)
karyawan.employees.emergency-contacts.edit   (GET)
karyawan.employees.emergency-contacts.update (PUT)
karyawan.employees.emergency-contacts.destroy (DELETE)
karyawan.employees.histories.index           (GET)

```

---

Publish Controllers
-------------------

[](#publish-controllers)

Publish controller ke aplikasi Anda untuk kustomisasi penuh:

```
# Controller API saja
php artisan vendor:publish --tag=karyawan-controllers-api

# Controller Web versi Inertia.js
php artisan vendor:publish --tag=karyawan-controllers-web-inertia

# Controller Web versi Blade
php artisan vendor:publish --tag=karyawan-controllers-web-blade
```

Controller disalin ke:

TagTujuan`karyawan-controllers-api``app/Http/Controllers/Karyawan/Api/``karyawan-controllers-web-inertia``app/Http/Controllers/Karyawan/Web/Inertia/``karyawan-controllers-web-blade``app/Http/Controllers/Karyawan/Web/Blade/`Setelah publish, perbarui namespace di setiap controller dari `Aliziodev\LaravelKaryawanCore\Http\Controllers\` menjadi `App\Http\Controllers\Karyawan\` dan arahkan route Anda ke controller yang baru.

---

Generator Kode Karyawan
-----------------------

[](#generator-kode-karyawan)

Kode dihasilkan dengan format `{PREFIX}{angka}`, contoh: `EMP00001`.

```
KARYAWAN_CODE_PREFIX=EMP
KARYAWAN_CODE_PAD_LENGTH=5
```

Dengan konfigurasi di atas, karyawan akan mendapatkan kode: `EMP00001`, `EMP00002`, …, `EMP99999`.

Generator menggunakan **pessimistic locking** (`lockForUpdate`) untuk mencegah kode duplikat saat ada request serentak. Kode karyawan yang sudah dihapus (soft delete) tidak akan pernah digunakan ulang.

---

Otorisasi (Policies)
--------------------

[](#otorisasi-policies)

Package ini dilengkapi policy default yang terbuka (semua diizinkan). Override di `AuthServiceProvider` Anda untuk membatasi akses:

```
// app/Policies/EmployeePolicy.php

use Aliziodev\LaravelKaryawanCore\Policies\EmployeePolicy as BasePolicy;
use Aliziodev\LaravelKaryawanCore\Models\Employee;

class EmployeePolicy extends BasePolicy
{
    public function viewSensitive(mixed $user, Employee $employee): bool
    {
        // Field sensitif: NIK, nomor KK, NPWP
        return $user->hasRole('hr-admin') || $user->id === $employee->user_id;
    }

    public function delete(mixed $user, Employee $employee): bool
    {
        return $user->hasRole('hr-admin');
    }
}
```

Daftarkan di `AuthServiceProvider`:

```
use Aliziodev\LaravelKaryawanCore\Models\Employee;
use App\Policies\EmployeePolicy;

protected $policies = [
    Employee::class => EmployeePolicy::class,
];
```

#### Method Policy yang Tersedia

[](#method-policy-yang-tersedia)

**EmployeePolicy:** `viewAny`, `view`, `viewSensitive`, `create`, `update`, `delete`, `changeStatus`, `linkUser`, `unlinkUser`

**EmployeeDocumentPolicy:** `viewAny`, `view`, `create`, `delete`

---

Referensi Enum
--------------

[](#referensi-enum)

Semua enum mengimplementasikan method `label(): string` yang mengembalikan label dalam bahasa Indonesia.

### ActiveStatus

[](#activestatus)

ValueLabel`isWorking()``active`Aktif`true``inactive`Tidak Aktif`false``resigned`Mengundurkan Diri`false``terminated`PHK`false``retired`Pensiun`false``long_leave`Cuti Panjang`true`### EmploymentType

[](#employmenttype)

ValueLabel`permanent`Karyawan Tetap`contract`Karyawan Kontrak`internship`Magang`daily_worker`Pekerja Harian Lepas`outsourcing`Outsourcing### DocumentType

[](#documenttype)

ValueLabel`ktp`KTP`kk`Kartu Keluarga`npwp`NPWP`contract`Kontrak Kerja`certificate`Sertifikat`diploma`Ijazah`cv`CV`other`Lainnya### Gender

[](#gender)

ValueLabel`male`Laki-laki`female`Perempuan### Religion

[](#religion)

ValueLabel`islam`Islam`kristen`Kristen`katolik`Katolik`hindu`Hindu`buddha`Buddha`konghucu`Konghucu`other`Lainnya### MaritalStatus

[](#maritalstatus)

ValueLabel`single`Belum Menikah`married`Menikah`divorced`Cerai Hidup`widowed`Cerai Mati### HistoryType

[](#historytype)

ValueLabel`status_change`Perubahan Status`position_change`Perubahan Jabatan`department_change`Perubahan Departemen`branch_change`Perubahan Cabang`company_change`Perubahan Perusahaan`employment_type_change`Perubahan Jenis Hubungan Kerja`manager_change`Perubahan Atasan`user_linked`Akun Login Dikaitkan`user_unlinked`Akun Login Dilepas---

Penggunaan Lanjutan
-------------------

[](#penggunaan-lanjutan)

### Mendengarkan Events

[](#mendengarkan-events)

Daftarkan listener di `EventServiceProvider`:

```
// app/Providers/EventServiceProvider.php

protected $listen = [
    \Aliziodev\LaravelKaryawanCore\Events\EmployeeCreated::class        => [
        \App\Listeners\KirimEmailSelamatDatang::class,
    ],
    \Aliziodev\LaravelKaryawanCore\Events\EmployeeStatusChanged::class  => [
        \App\Listeners\CabutAksesKaryawan::class,
    ],
    \Aliziodev\LaravelKaryawanCore\Events\EmployeeLinkedToUser::class   => [
        \App\Listeners\SinkronisasiHakAkses::class,
    ],
];
```

Contoh implementasi listener:

```
// app/Listeners/KirimEmailSelamatDatang.php

use Aliziodev\LaravelKaryawanCore\Events\EmployeeCreated;

class KirimEmailSelamatDatang
{
    public function handle(EmployeeCreated $event): void
    {
        $employee = $event->employee;

        if ($employee->work_email) {
            Mail::to($employee->work_email)->send(new EmailSelamatDatang($employee));
        }
    }
}
```

```
// app/Listeners/CabutAksesKaryawan.php

use Aliziodev\LaravelKaryawanCore\Events\EmployeeStatusChanged;
use Aliziodev\LaravelKaryawanCore\Enums\ActiveStatus;

class CabutAksesKaryawan
{
    public function handle(EmployeeStatusChanged $event): void
    {
        $statusTidakAktif = [
            ActiveStatus::Resigned,
            ActiveStatus::Terminated,
            ActiveStatus::Retired,
        ];

        if (in_array($event->newStatus, $statusTidakAktif)) {
            // Cabut semua token akses
            $event->employee->user?->tokens()->delete();
        }
    }
}
```

```
// app/Listeners/SinkronisasiHakAkses.php

use Aliziodev\LaravelKaryawanCore\Events\EmployeeLinkedToUser;

class SinkronisasiHakAkses
{
    public function handle(EmployeeLinkedToUser $event): void
    {
        $employee = $event->employee;

        // Berikan role berdasarkan jabatan karyawan
        $employee->user?->assignRole($employee->position->name);
    }
}
```

### Mengganti Generator Kode Karyawan

[](#mengganti-generator-kode-karyawan)

Buat implementasi kustom yang mengimplementasikan kontrak:

```
// app/Services/GeneratorKodeKaryawan.php

use Aliziodev\LaravelKaryawanCore\Contracts\EmployeeCodeGeneratorContract;
use Aliziodev\LaravelKaryawanCore\Models\Employee;

class GeneratorKodeKaryawan implements EmployeeCodeGeneratorContract
{
    public function generate(?string $prefix = null): string
    {
        // Contoh: format berbasis tahun → EMP2025-001
        $tahun  = date('Y');
        $jumlah = Employee::whereYear('join_date', $tahun)->count() + 1;

        return sprintf('%s%s-%03d', $prefix ?? 'EMP', $tahun, $jumlah);
    }
}
```

Daftarkan binding di service provider Anda:

```
// app/Providers/AppServiceProvider.php

use Aliziodev\LaravelKaryawanCore\Contracts\EmployeeCodeGeneratorContract;
use App\Services\GeneratorKodeKaryawan;

public function register(): void
{
    $this->app->bind(EmployeeCodeGeneratorContract::class, GeneratorKodeKaryawan::class);
}
```

### Mengganti Policies

[](#mengganti-policies)

Override method spesifik dengan memperluas class policy bawaan:

```
// app/Policies/EmployeePolicy.php

use Aliziodev\LaravelKaryawanCore\Policies\EmployeePolicy as BasePolicy;

class EmployeePolicy extends BasePolicy
{
    public function viewSensitive(mixed $user, Employee $employee): bool
    {
        return $user->hasPermissionTo('lihat-data-sensitif-karyawan');
    }

    public function delete(mixed $user, Employee $employee): bool
    {
        return $user->hasRole('hr-admin');
    }

    public function changeStatus(mixed $user, Employee $employee): bool
    {
        return $user->hasRole(['hr-admin', 'hr-manager']);
    }
}
```

### Memperluas Model

[](#memperluas-model)

Model package menggunakan `config('karyawan.table_names.*')` untuk resolusi tabel, sehingga dapat diperluas dengan bebas:

```
// app/Models/Employee.php

use Aliziodev\LaravelKaryawanCore\Models\Employee as BaseEmployee;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Employee extends BaseEmployee
{
    public function gajiPokok(): HasMany
    {
        return $this->hasMany(GajiPokok::class);
    }

    public function scopeByGolongan($query, string $golongan)
    {
        return $query->where('golongan', $golongan);
    }
}
```

Daftarkan binding agar action bawaan package menggunakan model yang diperluas:

```
// app/Providers/AppServiceProvider.php

public function register(): void
{
    $this->app->bind(
        \Aliziodev\LaravelKaryawanCore\Models\Employee::class,
        \App\Models\Employee::class
    );
}
```

### Menggunakan Model User yang Berbeda

[](#menggunakan-model-user-yang-berbeda)

Secara default package merujuk ke `App\Models\User`. Untuk menggantinya:

```
KARYAWAN_USER_MODEL=App\Models\Admin
```

Atau di `config/karyawan.php` setelah di-publish:

```
'user_model' => \App\Models\Admin::class,
```

### Menangani Exception

[](#menangani-exception)

Package melempar exception yang dapat ditangkap di controller atau exception handler:

```
use Aliziodev\LaravelKaryawanCore\Exceptions\EmployeeUserLinkException;
use Aliziodev\LaravelKaryawanCore\Exceptions\EmployeeCodeGenerationException;

class EmployeeUserController extends Controller
{
    public function __construct(
        private readonly LinkEmployeeUserAction $linkUserAction,
    ) {}

    public function store(LinkEmployeeUserRequest $request, Employee $employee): JsonResponse
    {
        try {
            $this->linkUserAction->execute($employee, (int) $request->user_id);
        } catch (EmployeeUserLinkException $e) {
            // Kemungkinan pesan:
            // - Karyawan sudah memiliki akun yang terhubung
            // - Akun user sudah terhubung ke karyawan lain
            return response()->json(['message' => $e->getMessage()], 409);
        }

        return response()->json(['message' => 'Akun berhasil dikaitkan.']);
    }
}
```

Untuk penanganan global, daftarkan di `bootstrap/app.php`:

```
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->render(function (EmployeeUserLinkException $e) {
        return response()->json(['message' => $e->getMessage()], 409);
    });
})
```

---

Pengujian
---------

[](#pengujian)

Package ini menggunakan [PestPHP](https://pestphp.com/) dengan database SQLite in-memory.

```
composer test
```

### Menggunakan Factory di Aplikasi Anda

[](#menggunakan-factory-di-aplikasi-anda)

```
use Aliziodev\LaravelKaryawanCore\Models\Company;
use Aliziodev\LaravelKaryawanCore\Models\Employee;

// Di TestCase::setUp()
Factory::guessFactoryNamesUsing(function (string $modelName) {
    return 'Aliziodev\\LaravelKaryawanCore\\Database\\Factories\\'
        . class_basename($modelName)
        . 'Factory';
});

// Di dalam test
$company  = Company::factory()->create(['is_active' => true]);
$employee = Employee::factory()->create(['company_id' => $company->id]);
```

### Konfigurasi TestCase untuk Package

[](#konfigurasi-testcase-untuk-package)

```
protected function getEnvironmentSetUp($app): void
{
    $app['config']->set('karyawan.routes.api.enabled', true);
    $app['config']->set('karyawan.routes.api.middleware', [
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ]);
}
```

> **Penting:** Route model binding memerlukan middleware `SubstituteBindings`. Tanpa middleware ini, model Eloquent tidak akan di-resolve dari parameter route saat pengujian.

---

Lisensi
-------

[](#lisensi)

Package ini adalah perangkat lunak open-source yang dilisensikan di bawah [lisensi MIT](LICENSE).

---

Dibuat dengan ❤️ untuk komunitas Laravel Indonesia oleh [Aliziodev](https://github.com/aliziodev)

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance89

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity54

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 87% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

4

Last Release

56d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/187039973?v=4)[Alizio](/maintainers/aliziodev)[@aliziodev](https://github.com/aliziodev)

---

Top Contributors

[![aliziodev](https://avatars.githubusercontent.com/u/187039973?v=4)](https://github.com/aliziodev "aliziodev (20 commits)")[![semantic-release-bot](https://avatars.githubusercontent.com/u/32174276?v=4)](https://github.com/semantic-release-bot "semantic-release-bot (3 commits)")

---

Tags

laravelcorehremployeeindonesiakaryawan

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/aliziodev-laravel-karyawan-core/health.svg)

```
[![Health](https://phpackages.com/badges/aliziodev-laravel-karyawan-core/health.svg)](https://phpackages.com/packages/aliziodev-laravel-karyawan-core)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9732.3M121](/packages/roots-acorn)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k14.1M120](/packages/laravel-pulse)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)[pressbooks/pressbooks

Pressbooks is an open source book publishing tool built on a WordPress multisite platform. Pressbooks outputs books in multiple formats, including PDF, EPUB, web, and a variety of XML flavours, using a theming/templating system, driven by CSS.

45344.0k1](/packages/pressbooks-pressbooks)[wearepixel/laravel-cart

A cart implementation for Laravel

1355.6k](/packages/wearepixel-laravel-cart)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
