30 Commits

Author SHA1 Message Date
Daeng Deni Mardaeni
4c4a4a33a9 fix(export): tambahkan properti parent_id pada BranchExport
- Menambahkan properti `protected $parent_id` pada kelas **BranchExport**.
- Properti ini digunakan untuk menyimpan ID cabang induk yang akan dipakai sebagai filter saat melakukan ekspor data.
- Memastikan nilai `parent_id` diinisialisasi melalui constructor dan dideklarasikan sebagai properti kelas.
- Perbaikan ini memastikan filter berdasarkan cabang induk berfungsi dengan benar pada proses ekspor.
- Mendukung konsistensi data pada ekspor **Excel/CSV** dengan filter yang akurat.
- Meningkatkan keandalan fitur ekspor cabang dalam modul laporan.
- Mengoptimalkan integrasi antara fitur filter dan proses ekspor.
- Memperbaiki potensi bug yang muncul akibat properti tidak dideklarasikan.
- Menjamin proses ekspor lebih stabil dan sesuai dengan parameter yang diberikan.
2025-09-08 15:12:27 +07:00
Daeng Deni Mardaeni
a2aabd51d6 refactor(basicdata): optimasi middleware autentikasi dan seeder perizinan
- Mengoptimalkan middleware autentikasi di controller berikut:
  - `BranchController`, `CurrencyController`, dan `HolidayCalendarController`.
  - Mengganti penggunaan `Auth::guard('web')->user()` menjadi middleware `auth`.
  - Menambahkan middleware closure untuk menetapkan properti `$this->user` setelah middleware `auth`.
  - Penyesuaian pada semua pemanggilan terkait autentikasi pengguna untuk menggunakan `$this->user`.

- Refaktor `PermissionSeeder`:
  - Menghapus metode `crudActions` beserta logika iterasi CRUD yang tidak digunakan.
  - Menyederhanakan proses seeding untuk model `PermissionGroup` dengan `updateOrCreate`.
  - Menghilangkan logika assignment izin dari seeder untuk mempercepat proses seeding.
2025-06-22 20:50:59 +07:00
Daeng Deni Mardaeni
c168634c3a feat(basicdata): tambahkan PermissionSeeder dan optimasi autentikasi di controller
- Menambahkan `PermissionSeeder` untuk inisialisasi data izin dengan struktur CRUD pada modul Basicdata.
  - Data izin mencakup tindakan seperti `create`, `read`, `update`, `delete`, `export`, `authorize`, `report`, dan `restore`.
  - Menyediakan relasi dengan grup izin menggunakan model `PermissionGroup`.
- Memanggil `PermissionSeeder` di `BasicdataDatabaseSeeder` untuk memastikan data izin terpasang saat proses seeding.

- Mengoptimalkan autentikasi pengguna di konstruktor controller:
  - Mengganti logika middleware pada `BranchController`, `CurrencyController`, dan `HolidayCalendarController` dengan properti `$this->user`.
  - Menggunakan `Auth::guard('web')->user()` sebagai standar pengelolaan autentikasi.
  - Menghapus middleware yang tidak diperlukan demi meningkatkan kinerja kode.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-06-22 19:00:31 +07:00
Daeng Deni Mardaeni
65695d0594 refactor(basicdata): update namespace Userstamps trait di model Base
- Mengganti namespace trait `Userstamps` dari `Wildside\Userstamps\Userstamps` ke `Mattiverse\Userstamps\Traits\Userstamps`.
- Perubahan ini bertujuan untuk menyesuaikan penggunaan namespace dengan struktur library terbaru.
- Menghapus import `Spatie\Activitylog\Facades\CauserResolver` yang tidak digunakan untuk merapikan kode.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-06-22 10:31:07 +07:00
putrakuningan
9158005a5c Merge pull request 'flpp' (#2) from flpp into master
Reviewed-on: #2
2025-06-22 10:25:11 +07:00
putrakuningan
336d74b628 Merge pull request 'FEAT: Fixed error permission on basic-data Module' (#1) from putrakuningan-patch-1 into master
Reviewed-on: #1
2025-06-21 11:29:21 +07:00
putrakuningan
5f9f07657f FEAT: Fixed error permission on basic-data Module 2025-06-21 11:28:59 +07:00
Sholahuddin Al Ayubi
05ceb5ef01 FEAT: Fixed error permission on basic-data Module 2025-06-20 14:59:19 +07:00
Sholahuddin Al Ayubi
89f5fedfd6 FEAT: Add New Userstamps 2025-06-19 16:26:45 +07:00
Daeng Deni Mardaeni
0a4f39cca1 refactor(basicdata): update namespace trait Userstamps pada model Base
- Mengganti namespace trait `Userstamps` dari `Wildside\Userstamps\Userstamps` menjadi `Mattiverse\Userstamps\Traits\Userstamps`.
- Penyesuaian ini bertujuan untuk memastikan kompatibilitas sistem dengan dependensi atau library terbaru.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-06-05 16:50:33 +07:00
Daeng Deni Mardaeni
9c0ee08c40 feat(branch): tambah kolom baru dan dukungan pencarian terkait data cabang
- Menambahkan kolom baru pada tabel `branches` melalui migrasi:
  - `address`
  - `mnemonic`
  - `customer_company`
  - `customer_mnemonic`
  - `company_group`
  - `curr_no`
  - `co_code`
  - `l_vendor_atm`
  - `l_vendor_cpc`
- Memperbarui model `Branch` agar mendukung kolom baru di properti `fillable`.
- Menambahkan dukungan pencarian berdasarkan kolom `address` pada:
  - `BranchExport.php` (untuk ekspor data)
  - `BranchController.php` (untuk API pencarian data cabang)
- Memperbarui tampilan daftar cabang (`branch/index.blade.php`) untuk menampilkan kolom `address`.
- Memperbarui format data ekspor cabang dengan menambahkan kolom `address`.
- Memperbaiki pengaturan format kolom tanggal pada data ekspor.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-05-20 21:57:13 +07:00
Daeng Deni Mardaeni
be271dbe6e feat(basicdata): tambahkan pencarian pada fitur ekspor Holiday Calendar
- Menambahkan kemampuan pencarian pada fitur ekspor Holiday Calendar.
- Mengubah konstruktor `HolidayCalendarExport` untuk menerima parameter `search` yang bersifat opsional.
- Menyesuaikan query pengambilan data pada `HolidayCalendarExport` agar mendukung filter berdasarkan:
  - Deskripsi (`description`).
  - Tipe (`type`).
  - Tanggal (`date`).
- Memperbaiki logika perhitungan halaman aktif pada pagination di `HolidayCalendarController`.
- Menambahkan parameter `search` pada fungsi `export` di `HolidayCalendarController`.
- Memperbarui URL export pada tampilan `index.blade.php` ketika input pencarian diubah.
- Menambahkan fungsi JavaScript `updateExportUrl()` untuk menyisipkan filter pencarian pada URL ekspor.
- Menjamin tidak ada perubahan URL ekspor jika pencarian kosong.

Fitur ini memungkinkan pengguna untuk mengekspor data kalender libur berdasarkan hasil pencarian spesifik.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-05-19 09:10:33 +07:00
Daeng Deni Mardaeni
8d190cb10d feat(currency): tambahkan fitur filter pada ekspor data mata uang
- Modifikasi `CurrencyController`:
  - Tambahkan parameter `Request` pada method `export` untuk menerima input filter `search`.
  - Perbarui logika ekspor agar mendukung pencarian berbasis parameter `search`.

- Perubahan pada `CurrencyExport`:
  - Tambahkan konstruktor untuk menerima dan menyimpan parameter pencarian (`search`).
  - Modifikasi query pada method `collection` untuk menambahkan filter berdasarkan `code`, `name`, atau `decimal_places` yang sesuai dengan parameter `search`.
  - Hapus kolom `updated_at` dan `deleted_at` dari output ekspor.
  - Perbaikan format data pada method `columnFormats`.

- Update pada view `currency/index.blade.php`:
  - Tambahkan elemen JavaScript untuk mengatur URL parameter `search` pada tombol ekspor.
  - Ganti event `change` pada input pencarian dengan `input` untuk meningkatkan respon pencarian secara real-time.
  - Pastikan URL ekspor diperbarui setiap kali pengguna mengetik dalam kolom pencarian.

- Penyesuaian minor pada `branch/index.blade.php`:
  - Hapus log yang tidak relevan sebelum proses ekspor.

Fitur ini memastikan proses ekspor data mata uang dapat disaring berdasarkan pencarian spesifik, memberikan fleksibilitas lebih kepada pengguna.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-05-19 09:08:33 +07:00
Daeng Deni Mardaeni
0bb12812a5 feat(branch): tambahkan fitur filter dan search pada eksport dan tabel cabang
- Menambahkan parameter `search` dan `parent_id` pada `BranchExport` untuk mendukung fitur filter.
- Memodifikasi method `collection` di `BranchExport` agar mendukung filter pencarian dan parent cabang.
- Memperbaiki issue pada method `collection` terkait penggunaan query `LOWER` untuk pencarian.
- Mengubah method `export` di `BranchController` agar menerima parameter filter dari request.
- Menambahkan logika filtering untuk `search` dan `parent_id` pada method index API `BranchController`.
- Menambahkan dropdown filter parent di tampilan `branch/index.blade.php`.
- Implementasi JavaScript di `branch/index.blade.php` untuk mendukung filter pencarian dan parent cabang.
  - Menambahkan logika sinkronisasi URL eksport dengan parameter filter.
  - Menambahkan event listener untuk filter pencarian dan dropdown parent.
  - Menambahkan validasi agar filter diterapkan ke datatable dan URL eksport secara dinamis.
- Memperbaiki penghitungan halaman pagination di datatable.
- Penyesuaian minor pada model Branch dan cara logging aktivitas di model Base.

Fitur ini memungkinkan pengguna melakukan filter data cabang berdasarkan pencarian dan parent cabang saat menampilkan tabel ataupun mengekspor data ke Excel.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-05-19 08:56:44 +07:00
Daeng Deni Mardaeni
4a644c3b5d feat(basicdata): tambahkan fitur relasi parent-child pada cabang
- Menambahkan kolom `parent_id` pada tabel `branches` dengan migrasi baru.
- Update model `Branch`:
  - Menambahkan relasi `parent()` untuk mendapatkan cabang induk.
  - Menambahkan relasi `children()` untuk mendapatkan anak cabang.
- Update `BranchController`:
  - Menampilkan daftar cabang induk saat membuat atau mengedit cabang.
  - Cek validasi agar cabang tidak bisa menjadi induk dirinya sendiri.
  - Tambahkan larangan hapus cabang jika memiliki anak cabang, baik untuk hapus tunggal maupun multiple.
- Update validation rules pada `BranchRequest` untuk memastikan validitas `parent_id`.
- Update tampilan:
  - Formulir pembuatan/edit cabang: Menampilkan dropdown untuk memilih cabang induk.
  - Daftar cabang: Menampilkan kolom untuk cabang induk.
- Tambahkan test unit:
  - Validasi relasi parent-child pada penyimpanan dan pembaruan cabang.
  - Melarang penghapusan cabang yang memiliki anak.
  - Memastikan perilaku relasi parent-child sesuai ekspektasi.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-05-18 15:13:52 +07:00
Daeng Deni Mardaeni
1998d89f84 refactor(basicdata): optimasi autentikasi dan pengelolaan user di controller
- Memindahkan autentikasi user dari metode `getUser` ke properti `user` di konstruktor controller.
- Mengganti semua pemanggilan metode `getUser` dengan properti `$this->user`.
- Memastikan validasi hak akses user menggunakan properti `$this->user` di seluruh fungsi controller:
  - `BranchController`
  - `CurrencyController`
  - `HolidayCalendarController`.
- Menghapus rute restore yang tidak digunakan pada Branch dan Currency.
- Menggunakan `Route::resource` untuk HolidayCalendarController agar lebih ringkas.
- Menambahkan dependensi `use Illuminate\Support\Facades\Auth` pada HolidayCalendarController demi konsistensi autentikasi.

Perubahan ini bertujuan untuk menyederhanakan pengelolaan user dan meningkatkan konsistensi autentikasi dalam modul.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-05-17 13:48:11 +07:00
Daeng Deni Mardaeni
4c6a6d8cea feat(holidaycalendar): implement full feature set for holiday calendar management
- Mengganti namespace model `HolidayCalendar` dari `Entities` ke `Models`.
- Menambahkan validasi izin untuk semua aksi CRUD dan ekspor pada `HolidayCalendarController`.
- Mengintegrasikan fitur izin pada tombol aksi (create, update, delete, export) di view `index.blade.php`.
- Mengupdate logika form view `create.blade.php` untuk mendukung pengelolaan izin dan action dinamis.
- Menambahkan class test `HolidayCalendarControllerTest` dengan pengujian lengkap mencakup:
  - Hak akses untuk membaca, membuat, memperbarui, menghapus, dan mengekspor data.
  - Validasi data saat penyimpanan/pembaruan.
  - Validasi respon HTTP untuk setiap aksi berdasarkan izin.
- Memastikan user tanpa izin akan menerima pesan atau pembatasan akses yang relevan (HTTP 403).
- Fitur ekspor CSV hanya dapat diakses oleh user dengan izin `basic-data.export`.
- Memperbaiki rendering tindakan pada data tabel di `index.blade.php` agar responsif terhadap izin user.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-05-17 11:34:12 +07:00
Daeng Deni Mardaeni
52b48263a2 feat(basicdata): tambah otorisasi berbasis peran dan pengujian pada BranchController
- Implementasi otorisasi berbasis peran untuk seluruh aksi di BranchController seperti index, create, store, edit, update, delete, dan export.
- Tambahan utilitas `getUser` untuk mendapatkan pengguna yang diautentikasi dan mempermudah pengecekan otorisasi.
- Semua aksi pada controller sekarang memeriksa izin pengguna sebelum melanjutkan:
  - `basic-data.read` untuk melihat data.
  - `basic-data.create` untuk membuat cabang baru.
  - `basic-data.update` untuk memperbarui data cabang.
  - `basic-data.delete` untuk menghapus data cabang.
  - `basic-data.export` untuk mengekspor data cabang.
- Penyesuaian pada view:
  - Tombol aksi seperti `Save`, `Delete Selected`, dan `Export to Excel` hanya tampil jika pengguna memiliki izin terkait.
- Tambahan pengujian (unit test) pada `BranchControllerTest` untuk memastikan logika otorisasi:
  - Pengguna dengan izin dapat melakukan aksi sesuai dengan perannya.
  - Pengguna tanpa izin mendapatkan respon 403 atau dicegah melakukan aksi tertentu.
- Update logika tombol aksi di datatables untuk mendukung pengecekan izin sebelum menampilkan opsi edit/hapus.
- Update respons JSON dalam aksi hapus tunggal dan hapus banyak untuk kejelasan struktur pesan.

Commit ini mengamankan BranchController dari akses tak sah dan meningkatkan fleksibilitas sistem terkait kendali peran dan izin.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-05-17 11:32:06 +07:00
Daeng Deni Mardaeni
32e620299b feat(currency): implement role-based access control, exports, and tests for currency management
- Menambahkan validasi Role-based Access Control (RBAC) untuk tindakan CRUD mata uang:
  1. Validasi untuk `read`, `create`, `update`, dan `delete` pada CurrencyController.
  2. Menambahkan metode `getUser()` untuk memperoleh user terautentikasi.
  3. Menangani respon dengan HTTP status `403 Forbidden` jika tidak memiliki izin.

- Memperbaiki rute dan logika `store` serta `update`:
  1. Validasi terhadap atribut `code` disesuaikan dengan skenario update (menggunakan ID).
  2. Menambahkan metode `authorize()` pada CurrencyRequest untuk memastikan izin aksi sesuai role (CRUD spesifik).

- Perubahan pada view blade:
  1. Menambahkan validasi izin sebelum rendering tombol `Tambah`, `Hapus`, `Export`, dan `Edit`.
  2. Menambahkan logika dinamis untuk izin terkait.

- Tambahan logika pada export ke Excel:
  1. Validasi izin untuk `basic-data.export` sebelum mengunduh file.

- Test Feature dengan PHPUnit:
  1. Menambahkan test coverage untuk tindakan CRUD, validasi izin role, dan ekspor data.
  2. Menggunakan database segar dengan RefreshDatabase.

- Refactor penggunaan model Currency di `CurrencyExport` agar sesuai namespace setelah modifikasi.

- Respon di `destroy` dan `deleteMultiple` dikembalikan dalam format JSON untuk standardisasi.

- Memastikan test mencakup berbagai skenario:
  1. User dengan izin vs tanpa izin.
  2. Operasi data valid dan tidak valid.

Penyesuaian ini meningkatkan keamanan dan manajemen peran pada modul Currency, serta memastikan pengujian yang mendalam terhadap semua fitur baru.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-05-17 11:28:17 +07:00
Daeng Deni Mardaeni
3a4c5bf4ca feat(basicdata): tambahkan helper untuk penghitungan hari kerja dan pembaruan format mata uang
- Menambahkan helper `workingDays` untuk menghitung jumlah hari kerja antara dua tanggal.
- Menambahkan helper `holidays` untuk mendapatkan daftar tanggal libur.
- Memperbarui fungsi `format_currency` menjadi `currencyFormat` dengan menambahkan dokumentasi yang lebih jelas.
- Mengubah format desimal mata uang IDR menjadi 2 desimal di `CurrencySeeder`.
- Menambahkan file `HolidayCalendar.php` ke dalam daftar autoload di `module.json`.
- Menambahkan proses truncate sebelum insert data di seeder `CurrencySeeder`.
2025-05-04 19:17:04 +07:00
Daeng Deni Mardaeni
9049075b0f feat(basicdata): tambahkan helper format_currency
- Menambahkan file helper `Currency.php` untuk format mata uang.
- Menyediakan fungsi `format_currency` yang mendukung parameter angka dan kode mata uang.
- Memperbarui `module.json` untuk memuat file helper secara otomatis.
2025-04-27 19:08:41 +07:00
Daeng Deni Mardaeni
7742df5d67 fix(currency): perbaiki logika pagination dan pencarian
- Ubah perhitungan jumlah halaman untuk menggunakan jumlah record yang difilter.
- Sesuaikan pengambilan nomor halaman saat ini dari request.
- Ganti event listener pencarian dari 'input' menjadi 'change' untuk meningkatkan performa.
- Tambahkan pemanggilan fungsi untuk mengatur ulang halaman ke 1 saat pencarian dilakukan.
2025-04-27 19:03:11 +07:00
Daeng Deni Mardaeni
1b5a5b59b6 feat(basicdata): tambahkan pemanggilan seeder untuk data dasar
- Menambahkan pemanggilan seeder untuk:
  - BranchesSeeder
  - CurrencySeeder
  - HolidayCalendarSeeder
2025-04-26 19:38:17 +07:00
Daeng Deni Mardaeni
996041d1dd feat(holiday_calendar): tambahkan seeder untuk data kalender libur
- Menambahkan seeder untuk mengisi tabel holiday_calendars dengan data libur nasional dan cuti bersama tahun 2025.
- Setiap entri mencakup tanggal, deskripsi, jenis libur, serta timestamp untuk created_at dan updated_at.
2025-04-26 19:38:12 +07:00
Daeng Deni Mardaeni
2341f202bf feat(currency): tambahkan seeder untuk data mata uang
- Menambahkan seeder baru untuk mengisi tabel currencies dengan data mata uang.
- Memperbarui daftar mata uang dengan simbol dan detail lainnya.
- Menggunakan Carbon untuk timestamp created_at dan updated_at.
2025-04-26 19:36:43 +07:00
Daeng Deni Mardaeni
1b7474131d feat(branches): tambahkan seeder untuk data cabang
- Menambahkan kelas BranchesSeeder untuk mengisi tabel branches.
- Menggunakan Carbon untuk menetapkan waktu pembuatan dan pembaruan.
- Memasukkan data cabang dengan atribut kode, nama, status, dan timestamp.
2025-04-26 19:35:40 +07:00
Daeng Deni Mardaeni
1b692215b8 feat(currency): tambahkan atribut simbol dan perbarui tampilan
- Menambahkan kolom simbol pada tabel mata uang di halaman index.
- Menambahkan input simbol pada form pembuatan mata uang.
- Memperbarui aturan validasi untuk simbol pada CurrencyRequest.
- Memperbarui model Currency untuk menyertakan atribut simbol.
- Memperbarui migrasi untuk menambahkan kolom simbol pada tabel currencies.
2025-04-26 19:34:44 +07:00
Daeng Deni Mardaeni
2e52155eda fix(branch): perbarui aturan validasi kode cabang
- Memperpanjang panjang maksimum kode cabang dari 3 menjadi 10 karakter.
- Menggunakan aturan unik yang mempertimbangkan kolom deleted_at untuk menghindari konflik dengan cabang yang dihapus.
2025-03-13 09:41:23 +07:00
Daeng Deni Mardaeni
6abea3a826 fix(export): perbaiki penggunaan model Branch pada ekspor cabang
- Mengubah namespace model Branch dari Modules\Lpj\Models\Branch
  menjadi Modules\Basicdata\Models\Branch
- Memastikan ekspor data cabang menggunakan model yang benar
2025-03-13 09:31:22 +07:00
Daeng Deni Mardaeni
58672dada3 fix(branch): perbaiki pesan konfirmasi penghapusan cabang
- Mengubah pesan konfirmasi dari 'User has been deleted.' menjadi 'Branch has been deleted.'
- Memastikan pesan yang ditampilkan sesuai dengan konteks penghapusan data cabang.
2025-03-13 09:06:48 +07:00
32 changed files with 3216 additions and 216 deletions

View File

@@ -6,14 +6,43 @@
use Maatwebsite\Excel\Concerns\WithColumnFormatting; use Maatwebsite\Excel\Concerns\WithColumnFormatting;
use Maatwebsite\Excel\Concerns\WithHeadings; use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping; use Maatwebsite\Excel\Concerns\WithMapping;
use Modules\Lpj\Models\Branch; use Modules\Basicdata\Models\Branch;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
class BranchExport implements WithColumnFormatting, WithHeadings, FromCollection, withMapping class BranchExport implements WithColumnFormatting, WithHeadings, FromCollection, WithMapping
{ {
protected $search;
protected $parent_id;
public function __construct($search = null, $parent_id = null)
{
$this->search = $search;
$this->parent_id = $parent_id;
}
public function collection() public function collection()
{ {
return Branch::all(); $query = Branch::query();
if (!empty($this->search)) {
$search = strtolower($this->search);
$query->where(function ($q) use ($search) {
$q->whereRaw('LOWER(code) LIKE ?', ['%' . $search . '%'])
->orWhereRaw('LOWER(name) LIKE ?', ['%' . $search . '%'])
->orWhereRaw('LOWER(address) LIKE ?', ['%' . $search . '%'])
->orWhereHas('parent', function ($q) use ($search) {
$q->whereRaw('LOWER(name) LIKE ?', ['%' . strtolower($search) . '%']);
});
});
}
// Apply parent filter if provided
if (isset($this->parent_id) && !empty($this->parent_id)) {
$parentId = $this->parent_id;
$query->where('parent_id', $parentId);
}
return $query->get();
} }
public function map($row) public function map($row)
@@ -23,6 +52,8 @@
$row->id, $row->id,
$row->code, $row->code,
$row->name, $row->name,
$row->parent ? $row->parent->name : '',
$row->address,
$row->created_at $row->created_at
]; ];
} }
@@ -34,6 +65,8 @@
'ID', 'ID',
'Code', 'Code',
'Name', 'Name',
'Parent Branch',
'Address',
'Created At' 'Created At'
]; ];
} }
@@ -43,7 +76,7 @@
{ {
return [ return [
'A' => NumberFormat::FORMAT_NUMBER, 'A' => NumberFormat::FORMAT_NUMBER,
'D' => NumberFormat::FORMAT_DATE_DATETIME 'E' => NumberFormat::FORMAT_DATE_DATETIME
]; ];
} }
} }

View File

@@ -6,14 +6,33 @@
use Maatwebsite\Excel\Concerns\WithColumnFormatting; use Maatwebsite\Excel\Concerns\WithColumnFormatting;
use Maatwebsite\Excel\Concerns\WithHeadings; use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping; use Maatwebsite\Excel\Concerns\WithMapping;
use Modules\Lpj\Models\Currency; use Modules\Basicdata\Models\Currency;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
class CurrencyExport implements WithColumnFormatting, WithHeadings, FromCollection, withMapping class CurrencyExport implements WithColumnFormatting, WithHeadings, FromCollection, WithMapping
{ {
protected $search;
public function __construct($search = null)
{
$this->search = $search;
}
public function collection() public function collection()
{ {
return Currency::all(); $query = Currency::query();
if (!empty($this->search)) {
$search = $this->search;
$query->where(function ($q) use ($search) {
$q->whereRaw('LOWER(code) LIKE ?', ['%' . strtolower($search) . '%'])
->orWhereRaw('LOWER(name) LIKE ?', ['%' . strtolower($search) . '%'])
->orWhereRaw('CAST(decimal_places AS TEXT) LIKE ?', ['%' . $search . '%']);
});
}
return $query->get();
} }
public function map($row) public function map($row)
@@ -24,8 +43,6 @@
$row->code, $row->code,
$row->name, $row->name,
$row->decimal_places, $row->decimal_places,
$row->updated_at,
$row->deleted_at,
$row->created_at $row->created_at
]; ];
} }
@@ -47,7 +64,7 @@
{ {
return [ return [
'A' => NumberFormat::FORMAT_NUMBER, 'A' => NumberFormat::FORMAT_NUMBER,
'B' => NumberFormat::FORMAT_NUMBER, 'D' => NumberFormat::FORMAT_NUMBER,
'E' => NumberFormat::FORMAT_DATE_DATETIME 'E' => NumberFormat::FORMAT_DATE_DATETIME
]; ];
} }

View File

@@ -6,14 +6,32 @@ use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithColumnFormatting; use Maatwebsite\Excel\Concerns\WithColumnFormatting;
use Maatwebsite\Excel\Concerns\WithHeadings; use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping; use Maatwebsite\Excel\Concerns\WithMapping;
use Modules\Basicdata\Entities\HolidayCalendar; use Modules\Basicdata\Models\HolidayCalendar;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
class HolidayCalendarExport implements WithColumnFormatting, WithHeadings, FromCollection, WithMapping class HolidayCalendarExport implements WithColumnFormatting, WithHeadings, FromCollection, WithMapping
{ {
protected $search;
public function __construct($search = null)
{
$this->search = $search;
}
public function collection() public function collection()
{ {
return HolidayCalendar::all(); $query = HolidayCalendar::query();
if (!empty($this->search)) {
$search = $this->search;
$query->where(function ($q) use ($search) {
$q->whereRaw('LOWER(description) LIKE ?', ['%' . strtolower($search) . '%'])
->orWhereRaw('LOWER(type) LIKE ?', ['%' . strtolower($search) . '%'])
->orWhereRaw('CAST(date AS TEXT) LIKE ?', ['%' . $search . '%']);
});
}
return $query->get();
} }
public function map($row): array public function map($row): array

22
app/Helpers/Currency.php Normal file
View File

@@ -0,0 +1,22 @@
<?php
use Modules\Basicdata\Models\Currency;
/**
* Format a number as a currency string with appropriate symbol and formatting.
*
* This function retrieves currency information from the database and formats
* the provided number according to the currency's specifications.
*
* @param float|int $number The number to be formatted as currency
* @param string $currency The currency code (default: 'IDR')
* @return string The formatted currency string with symbol and proper number formatting
*/
if(!function_exists('currencyFormat')) {
function currencyFormat($number, $currency = 'IDR') {
$currency = Currency::where('code', $currency)->first();
$symbol = $currency->symbol?? '';
return $symbol . ' ' . number_format($number, $currency->decimal_places, ',', '.');
}
}

View File

@@ -0,0 +1,54 @@
<?php
use Carbon\Carbon;
use Modules\Basicdata\Models\HolidayCalendar;
/**
* Calculate the number of working days between two dates.
*
* This function counts the number of days that are not weekends and not holidays
* between the given start and end dates (inclusive).
*
* @param string|Carbon $startDate The starting date for the calculation
* @param string|Carbon $endDate The ending date for the calculation
* @return int The number of working days between the two dates
*/
if (!function_exists('workingDays')) {
function workingDays($startDate, $endDate)
{
$startDate = Carbon::parse($startDate)->startOfDay();
$endDate = Carbon::parse($endDate)->endOfDay();
$workingDay = 0;
$now = $startDate->copy();
while ($now <= $endDate) {
if (!$now->isWeekend() && !in_array($now->format('Y-m-d'), holidays())) {
$workingDay++;
}
$now->addDay();
}
return $workingDay;
}
}
/**
* Get a list of all holiday dates from the holiday calendar.
*
* This function retrieves all holiday dates from the HolidayCalendar model
* and formats them as Y-m-d strings for easy comparison in date calculations.
*
* @return array An array of holiday dates in Y-m-d format
*/
if (!function_exists('holidays')) {
function holidays()
{
return HolidayCalendar::pluck('date')->map(
function ($item) {
return Carbon::parse($item)->format('Y-m-d');
},
)->toArray();
}
}

View File

@@ -2,6 +2,7 @@
namespace Modules\Basicdata\Http\Controllers; namespace Modules\Basicdata\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Exception; use Exception;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -12,15 +13,37 @@
class BranchController extends Controller class BranchController extends Controller
{ {
public $user; protected $user;
public function __construct()
{
// Mengatur middleware auth
$this->middleware('auth');
// Mengatur user setelah middleware auth dijalankan
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
return $next($request);
});
}
public function index() public function index()
{ {
// Check if the authenticated user has the required permission to view branches
if (is_null($this->user) || !$this->user->can('basic-data.read')) {
abort(403, 'Sorry! You are not allowed to view branches.');
}
return view('basicdata::branch.index'); return view('basicdata::branch.index');
} }
public function store(BranchRequest $request) public function store(BranchRequest $request)
{ {
// Check if the authenticated user has the required permission to create branches
if (is_null($this->user) || !$this->user->can('basic-data.create')) {
abort(403, 'Sorry! You are not allowed to create branches.');
}
$validate = $request->validated(); $validate = $request->validated();
if ($validate) { if ($validate) {
@@ -40,19 +63,43 @@
public function create() public function create()
{ {
return view('basicdata::branch.create'); // Check if the authenticated user has the required permission to create branches
if (is_null($this->user) || !$this->user->can('basic-data.create')) {
abort(403, 'Sorry! You are not allowed to create branches.');
}
$branches = Branch::all();
return view('basicdata::branch.create', compact('branches'));
} }
public function edit($id) public function edit($id)
{ {
$branch = Branch::find($id); // Check if the authenticated user has the required permission to update branches
return view('basicdata::branch.create', compact('branch')); if (is_null($this->user) || !$this->user->can('basic-data.update')) {
abort(403, 'Sorry! You are not allowed to update branches.');
}
$branch = Branch::findOrFail($id);
$branches = Branch::all();
return view('basicdata::branch.create', compact('branch', 'branches'));
} }
public function update(BranchRequest $request, $id) public function update(BranchRequest $request, $id)
{ {
// Check if the authenticated user has the required permission to update branches
if (is_null($this->user) || !$this->user->can('basic-data.update')) {
abort(403, 'Sorry! You are not allowed to update branches.');
}
$validate = $request->validated(); $validate = $request->validated();
// Tambahkan validasi manual untuk memeriksa parent_id
if (isset($validate['parent_id']) && $validate['parent_id'] == $id) {
return redirect()
->back()
->withInput()
->withErrors(['parent_id' => 'Cabang tidak dapat menjadi induk dari dirinya sendiri.']);
}
if ($validate) { if ($validate) {
try { try {
// Update in database // Update in database
@@ -71,28 +118,71 @@
public function destroy($id) public function destroy($id)
{ {
// Check if the authenticated user has the required permission to delete branches
if (is_null($this->user) || !$this->user->can('basic-data.delete')) {
return response()->json([
'success' => false,
'message' => 'Sorry! You are not allowed to delete branches.'
], 403);
}
try { try {
// Delete from database // Find the branch
$branch = Branch::find($id); $branch = Branch::find($id);
// Check if the branch has children
if ($branch->children()->exists()) {
return response()->json([
'success' => false,
'message' => 'Cabang dengan anak cabang tidak dapat dihapus.'
], 422);
}
// Delete from database
$branch->delete(); $branch->delete();
echo json_encode(['success' => true, 'message' => 'Branch deleted successfully']); return response()->json(['success' => true, 'message' => 'Branch deleted successfully']);
} catch (Exception $e) { } catch (Exception $e) {
echo json_encode(['success' => false, 'message' => 'Failed to delete branch']); return response()->json(['success' => false, 'message' => 'Failed to delete branch']);
} }
} }
public function deleteMultiple(Request $request) public function deleteMultiple(Request $request)
{ {
// Check if the authenticated user has the required permission to delete branches
if (is_null($this->user) || !$this->user->can('basic-data.delete')) {
return response()->json([
'success' => false,
'message' => 'Sorry! You are not allowed to delete branches.'
], 403);
}
$ids = $request->input('ids'); $ids = $request->input('ids');
// Check if any of the branches have children
$branchesWithChildren = Branch::whereIn('id', $ids)
->whereHas('children')
->get();
if ($branchesWithChildren->count() > 0) {
return response()->json([
'success' => false,
'message' => 'Beberapa cabang memiliki anak cabang dan tidak dapat dihapus.'
], 422);
}
Branch::whereIn('id', $ids)->delete(); Branch::whereIn('id', $ids)->delete();
return response()->json(['message' => 'Branches deleted successfully']); return response()->json(['success' => true, 'message' => 'Branches deleted successfully']);
} }
public function dataForDatatables(Request $request) public function dataForDatatables(Request $request)
{ {
if (is_null($this->user) || !$this->user->can('branch.view')) { // Check if the authenticated user has the required permission to view branches
//abort(403, 'Sorry! You are not allowed to view users.'); if (is_null($this->user) || !$this->user->can('basic-data.read')) {
return response()->json([
'success' => false,
'message' => 'Sorry! You are not allowed to view branches.'
], 403);
} }
// Retrieve data from the database // Retrieve data from the database
@@ -100,11 +190,25 @@
// Apply search filter if provided // Apply search filter if provided
if ($request->has('search') && !empty($request->get('search'))) { if ($request->has('search') && !empty($request->get('search'))) {
$search = $request->get('search'); $search = json_decode($request->get('search'));
$query->where(function ($q) use ($search) {
$q->where('code', 'LIKE', "%$search%"); if(isset($search->search)) {
$q->orWhere('name', 'LIKE', "%$search%"); $search_ = strtolower($search->search);
$query->where(function ($q) use ($search_) {
$q->whereRaw('LOWER(code) LIKE ?', ['%' . strtolower($search_) . '%']);
$q->orWhereRaw('LOWER(name) LIKE ?', ['%' . strtolower($search_) . '%']);
$q->orWhereRaw('LOWER(address) LIKE ?', ['%' . strtolower($search_) . '%']);
$q->orWhereHas('parent', function ($q) use ($search_) {
$q->whereRaw('LOWER(name) LIKE ?', ['%' . strtolower($search_) . '%']);
}); });
});
}
// Apply parent filter if provided
if (isset($search->parent_id) && !empty($search->parent_id)) {
$parentId = $search->parent_id;
$query->where('parent_id', $parentId);
}
} }
// Apply sorting if provided // Apply sorting if provided
@@ -132,11 +236,22 @@
// Get the data for the current page // Get the data for the current page
$data = $query->get(); $data = $query->get();
$data = $data->map(function ($item) {
return [
'id' => $item->id,
'code' => $item->code,
'name' => $item->name,
'parent_id' => $item->parent?->name ?? null,
'address' => str_replace(']', "", $item->address),
];
});
// Calculate the page count // Calculate the page count
$pageCount = ceil($totalRecords / $request->get('size')); $pageCount = ceil($totalRecords / $request->get('size'));
// Calculate the current page number // Calculate the current page number
$currentPage = 0 + 1; $currentPage = $request->get('page') ?: 1;
// Return the response data as a JSON object // Return the response data as a JSON object
return response()->json([ return response()->json([
@@ -150,8 +265,17 @@
]); ]);
} }
public function export() public function export(Request $request)
{ {
return Excel::download(new BranchExport, 'branch.xlsx'); // Check if the authenticated user has the required permission to export branches
if (is_null($this->user) || !$this->user->can('basic-data.export')) {
abort(403, 'Sorry! You are not allowed to export branches.');
}
// Get search parameter from request
$search = $request->get('search');
$parentId = $request->get('parent_id');
return Excel::download(new BranchExport($search,$parentId), 'branch.xlsx');
} }
} }

View File

@@ -9,18 +9,41 @@
use Modules\Basicdata\Exports\CurrencyExport; use Modules\Basicdata\Exports\CurrencyExport;
use Modules\Basicdata\Http\Requests\CurrencyRequest; use Modules\Basicdata\Http\Requests\CurrencyRequest;
use Modules\Basicdata\Models\Currency; use Modules\Basicdata\Models\Currency;
use Illuminate\Support\Facades\Auth;
class CurrencyController extends Controller class CurrencyController extends Controller
{ {
public $user; protected $user;
public function __construct()
{
// Mengatur middleware auth
$this->middleware('auth');
// Mengatur user setelah middleware auth dijalankan
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
return $next($request);
});
}
public function index() public function index()
{ {
// Check if the authenticated user has the required permission to view currencies
if (is_null($this->user) || !$this->user->can('basic-data.read')) {
abort(403, 'Sorry! You are not allowed to view currencies.');
}
return view('basicdata::currency.index'); return view('basicdata::currency.index');
} }
public function store(CurrencyRequest $request) public function store(CurrencyRequest $request)
{ {
// Check if the authenticated user has the required permission to create currencies
if (is_null($this->user) || !$this->user->can('basic-data.create')) {
abort(403, 'Sorry! You are not allowed to create currencies.');
}
$validate = $request->validated(); $validate = $request->validated();
if ($validate) { if ($validate) {
@@ -40,17 +63,32 @@
public function create() public function create()
{ {
// Check if the authenticated user has the required permission to create currencies
if (is_null($this->user) || !$this->user->can('basic-data.create')) {
abort(403, 'Sorry! You are not allowed to create currencies.');
}
return view('basicdata::currency.create'); return view('basicdata::currency.create');
} }
public function edit($id) public function edit($id)
{ {
// Check if the authenticated user has the required permission to update currencies
if (is_null($this->user) || !$this->user->can('basic-data.update')) {
abort(403, 'Sorry! You are not allowed to update currencies.');
}
$currency = Currency::find($id); $currency = Currency::find($id);
return view('basicdata::currency.create', compact('currency')); return view('basicdata::currency.create', compact('currency'));
} }
public function update(CurrencyRequest $request, $id) public function update(CurrencyRequest $request, $id)
{ {
// Check if the authenticated user has the required permission to update currencies
if (is_null($this->user) || !$this->user->can('basic-data.update')) {
abort(403, 'Sorry! You are not allowed to update currencies.');
}
$validate = $request->validated(); $validate = $request->validated();
if ($validate) { if ($validate) {
@@ -71,28 +109,39 @@
public function destroy($id) public function destroy($id)
{ {
// Check if the authenticated user has the required permission to delete currencies
if (is_null($this->user) || !$this->user->can('basic-data.delete')) {
return response()->json(['success' => false, 'message' => 'Sorry! You are not allowed to delete currencies.'], 403);
}
try { try {
// Delete from database // Delete from database
$currency = Currency::find($id); $currency = Currency::find($id);
$currency->delete(); $currency->delete();
echo json_encode(['success' => true, 'message' => 'Currency deleted successfully']); return response()->json(['success' => true, 'message' => 'Currency deleted successfully']);
} catch (Exception $e) { } catch (Exception $e) {
echo json_encode(['success' => false, 'message' => 'Failed to delete currency']); return response()->json(['success' => false, 'message' => 'Failed to delete currency']);
} }
} }
public function deleteMultiple(Request $request) public function deleteMultiple(Request $request)
{ {
// Check if the authenticated user has the required permission to delete currencies
if (is_null($this->user) || !$this->user->can('basic-data.delete')) {
return response()->json(['success' => false, 'message' => 'Sorry! You are not allowed to delete currencies.'], 403);
}
$ids = $request->input('ids'); $ids = $request->input('ids');
Currency::whereIn('id', $ids)->delete(); Currency::whereIn('id', $ids)->delete();
return response()->json(['message' => 'Currencies deleted successfully']); return response()->json(['success' => true, 'message' => 'Currencies deleted successfully']);
} }
public function dataForDatatables(Request $request) public function dataForDatatables(Request $request)
{ {
if (is_null($this->user) || !$this->user->can('currency.view')) { // Check if the authenticated user has the required permission to view currencies
//abort(403, 'Sorry! You are not allowed to view users.'); if (is_null($this->user) || !$this->user->can('basic-data.read')) {
return response()->json(['success' => false, 'message' => 'Sorry! You are not allowed to view currencies.'], 403);
} }
// Retrieve data from the database // Retrieve data from the database
@@ -103,6 +152,7 @@
$search = $request->get('search'); $search = $request->get('search');
$query->where(function ($q) use ($search) { $query->where(function ($q) use ($search) {
$q->where('code', 'LIKE', "%$search%"); $q->where('code', 'LIKE', "%$search%");
$q->orWhere('symbol', 'LIKE', "%$search%");
$q->orWhere('name', 'LIKE', "%$search%"); $q->orWhere('name', 'LIKE', "%$search%");
}); });
} }
@@ -133,10 +183,10 @@
$data = $query->get(); $data = $query->get();
// Calculate the page count // Calculate the page count
$pageCount = ceil($totalRecords / $request->get('size')); $pageCount = ceil($filteredRecords / ($request->get('size') ?: 1));
// Calculate the current page number // Calculate the current page number
$currentPage = 0 + 1; $currentPage = $request->get('page') ?: 1;
// Return the response data as a JSON object // Return the response data as a JSON object
return response()->json([ return response()->json([
@@ -150,8 +200,16 @@
]); ]);
} }
public function export() public function export(Request $request)
{ {
return Excel::download(new CurrencyExport, 'currency.xlsx'); // Check if the authenticated user has the required permission to export currencies
if (is_null($this->user) || !$this->user->can('basic-data.export')) {
abort(403, 'Sorry! You are not allowed to export currencies.');
}
// Get search parameter from request
$search = $request->get('search');
return Excel::download(new CurrencyExport($search), 'currency.xlsx');
} }
} }

View File

@@ -9,18 +9,42 @@
use Modules\Basicdata\Exports\HolidayCalendarExport; use Modules\Basicdata\Exports\HolidayCalendarExport;
use Modules\Basicdata\Http\Requests\HolidayCalendarRequest; use Modules\Basicdata\Http\Requests\HolidayCalendarRequest;
use Modules\Basicdata\Models\HolidayCalendar; use Modules\Basicdata\Models\HolidayCalendar;
use Illuminate\Support\Facades\Auth;
class HolidayCalendarController extends Controller class HolidayCalendarController extends Controller
{ {
public $user; protected $user;
public function __construct()
{
// Mengatur middleware auth
$this->middleware('auth');
// Mengatur user setelah middleware auth dijalankan
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
return $next($request);
});
}
public function index() public function index()
{ {
// Check if the authenticated user has the required permission to view holiday calendars
if (is_null($this->user) || !$this->user->can('basic-data.read')) {
abort(403, 'Sorry! You are not allowed to view holiday calendars.');
}
return view('basicdata::holidaycalendar.index'); return view('basicdata::holidaycalendar.index');
} }
public function store(HolidayCalendarRequest $request) public function store(HolidayCalendarRequest $request)
{ {
// Check if the authenticated user has the required permission to create holiday calendars
if (is_null($this->user) || !$this->user->can('basic-data.create')) {
abort(403, 'Sorry! You are not allowed to create holiday calendars.');
}
$validate = $request->validated(); $validate = $request->validated();
if ($validate) { if ($validate) {
@@ -40,17 +64,32 @@
public function create() public function create()
{ {
// Check if the authenticated user has the required permission to create holiday calendars
if (is_null($this->user) || !$this->user->can('basic-data.create')) {
abort(403, 'Sorry! You are not allowed to create holiday calendars.');
}
return view('basicdata::holidaycalendar.create'); return view('basicdata::holidaycalendar.create');
} }
public function edit($id) public function edit($id)
{ {
// Check if the authenticated user has the required permission to update holiday calendars
if (is_null($this->user) || !$this->user->can('basic-data.update')) {
abort(403, 'Sorry! You are not allowed to update holiday calendars.');
}
$holiday = HolidayCalendar::find($id); $holiday = HolidayCalendar::find($id);
return view('basicdata::holidaycalendar.create', compact('holiday')); return view('basicdata::holidaycalendar.create', compact('holiday'));
} }
public function update(HolidayCalendarRequest $request, $id) public function update(HolidayCalendarRequest $request, $id)
{ {
// Check if the authenticated user has the required permission to update holiday calendars
if (is_null($this->user) || !$this->user->can('basic-data.update')) {
abort(403, 'Sorry! You are not allowed to update holiday calendars.');
}
$validate = $request->validated(); $validate = $request->validated();
if ($validate) { if ($validate) {
@@ -74,6 +113,11 @@
public function destroy($id) public function destroy($id)
{ {
// Check if the authenticated user has the required permission to delete holiday calendars
if (is_null($this->user) || !$this->user->can('basic-data.delete')) {
abort(403, 'Sorry! You are not allowed to delete holiday calendars.');
}
try { try {
$holiday = HolidayCalendar::find($id); $holiday = HolidayCalendar::find($id);
$holiday->delete(); $holiday->delete();
@@ -90,15 +134,27 @@
public function deleteMultiple(Request $request) public function deleteMultiple(Request $request)
{ {
// Check if the authenticated user has the required permission to delete holiday calendars
if (is_null($this->user) || !$this->user->can('basic-data.delete')) {
return response()->json([
'success' => false,
'message' => 'Sorry! You are not allowed to delete holiday calendars.'
], 403);
}
$ids = $request->input('ids'); $ids = $request->input('ids');
HolidayCalendar::whereIn('id', $ids)->delete(); HolidayCalendar::whereIn('id', $ids)->delete();
return response()->json(['message' => 'Holidays deleted successfully']); return response()->json(['success' => true, 'message' => 'Holidays deleted successfully']);
} }
public function dataForDatatables(Request $request) public function dataForDatatables(Request $request)
{ {
if (is_null($this->user) || !$this->user->can('currency.view')) { // Check if the authenticated user has the required permission to view holiday calendars
//abort(403, 'Sorry! You are not allowed to view users.'); if (is_null($this->user) || !$this->user->can('basic-data.read')) {
return response()->json([
'success' => false,
'message' => 'Sorry! You are not allowed to view holiday calendars.'
], 403);
} }
// Retrieve data from the database // Retrieve data from the database
@@ -143,7 +199,7 @@
$pageCount = ceil($totalRecords / $request->get('size')); $pageCount = ceil($totalRecords / $request->get('size'));
// Calculate the current page number // Calculate the current page number
$currentPage = 0 + 1; $currentPage = $request->get('page') ?: 1;
// Return the response data as a JSON object // Return the response data as a JSON object
return response()->json([ return response()->json([
@@ -157,8 +213,16 @@
]); ]);
} }
public function export() public function export(Request $request)
{ {
return Excel::download(new HolidayCalendarExport, 'holiday_calendar.xlsx'); // Check if the authenticated user has the required permission to export holiday calendars
if (is_null($this->user) || !$this->user->can('basic-data.export')) {
abort(403, 'Sorry! You are not allowed to export holiday calendars.');
}
// Get search parameter from request
$search = $request->get('search');
return Excel::download(new HolidayCalendarExport($search), 'holiday_calendar.xlsx');
} }
} }

View File

@@ -3,6 +3,7 @@
namespace Modules\Basicdata\Http\Requests; namespace Modules\Basicdata\Http\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class BranchRequest extends FormRequest class BranchRequest extends FormRequest
{ {
@@ -14,6 +15,15 @@
{ {
$rules = [ $rules = [
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
'parent_id' => [
'nullable',
'exists:branches,id',
function ($attribute, $value, $fail) {
if ($value == $this->route('branch')) {
$fail('Cabang tidak dapat menjadi induk dari dirinya sendiri.');
}
},
],
'status' => 'nullable|boolean', 'status' => 'nullable|boolean',
'authorized_at' => 'nullable|datetime', 'authorized_at' => 'nullable|datetime',
'authorized_status' => 'nullable|string|max:1', 'authorized_status' => 'nullable|string|max:1',
@@ -21,9 +31,23 @@
]; ];
if ($this->method() == 'PUT') { if ($this->method() == 'PUT') {
$rules['code'] = 'required|string|max:3|unique:branches,code,' . $this->id; $rules['code'] = [
'required',
'string',
'max:10',
Rule::unique('branches')->ignore($this->id)->where(function ($query) {
return $query->whereNull('deleted_at');
}),
];
} else { } else {
$rules['code'] = 'required|string|max:3|unique:branches,code'; $rules['code'] = [
'required',
'string',
'max:10',
Rule::unique('branches')->where(function ($query) {
return $query->whereNull('deleted_at');
}),
];
} }
return $rules; return $rules;

View File

@@ -14,6 +14,7 @@
{ {
$rules = [ $rules = [
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
'symbol' => 'required|string|max:10',
'decimal_places' => 'nullable|integer|between:0,3', 'decimal_places' => 'nullable|integer|between:0,3',
'status' => 'nullable|boolean', 'status' => 'nullable|boolean',
'authorized_at' => 'nullable|datetime', 'authorized_at' => 'nullable|datetime',
@@ -22,7 +23,8 @@
]; ];
if ($this->method() == 'PUT') { if ($this->method() == 'PUT') {
$rules['code'] = 'required|string|max:3|unique:currencies,code,' . $this->id; $id = $this->id ? (int)$this->id : null;
$rules['code'] = 'required|string|max:3|unique:currencies,code,' . $id;
} else { } else {
$rules['code'] = 'required|string|max:3|unique:currencies,code'; $rules['code'] = 'required|string|max:3|unique:currencies,code';
} }
@@ -36,6 +38,14 @@
public function authorize() public function authorize()
: bool : bool
{ {
$user = auth()->guard('web')->user();
if ($this->method() == 'PUT') {
return $user && $user->can('basic-data.update');
} elseif ($this->method() == 'POST') {
return $user && $user->can('basic-data.create');
}
return true; return true;
} }
} }

View File

@@ -6,7 +6,7 @@
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Activitylog\LogOptions; use Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Traits\LogsActivity; use Spatie\Activitylog\Traits\LogsActivity;
use Wildside\Userstamps\Userstamps; use Mattiverse\Userstamps\Traits\Userstamps;
/** /**
@@ -46,6 +46,6 @@
public function getActivitylogOptions() public function getActivitylogOptions()
: LogOptions : LogOptions
{ {
return LogOptions::defaults()->logAll()->useLogName('LPJ : '); return LogOptions::defaults()->logAll()->useLogName('Basic Data');
} }
} }

View File

@@ -2,9 +2,41 @@
namespace Modules\Basicdata\Models; namespace Modules\Basicdata\Models;
class Branch extends Base class Branch extends Base
{ {
protected $table = 'branches'; protected $table = 'branches';
protected $fillable = ['code', 'name', 'status', 'authorized_at', 'authorized_status', 'authorized_by']; protected $fillable = [
'code',
'name',
'address',
'mnemonic',
'customer_company',
'customer_mnemonic',
'company_group',
'curr_no',
'co_code',
'l_vendor_atm',
'l_vendor_cpc',
'status',
'authorized_at',
'authorized_status',
'authorized_by',
'parent_id'
];
/**
* Get the parent branch of this branch
*/
public function parent()
{
return $this->belongsTo(Branch::class, 'parent_id');
}
/**
* Get the child branches of this branch
*/
public function children()
{
return $this->hasMany(Branch::class, 'parent_id');
}
} }

View File

@@ -9,6 +9,7 @@
protected $fillable = [ protected $fillable = [
'code', 'code',
'name', 'name',
'symbol',
'decimal_places', 'decimal_places',
'status', 'status',
'authorized_at', 'authorized_at',

View File

@@ -15,6 +15,7 @@
$table->id(); $table->id();
$table->string('code', 3)->unique(); $table->string('code', 3)->unique();
$table->string('name'); $table->string('name');
$table->string('symbol')->nullable();
$table->integer('decimal_places')->default(2); $table->integer('decimal_places')->default(2);
$table->boolean('status')->default(true)->nullable(); $table->boolean('status')->default(true)->nullable();
$table->timestamps(); $table->timestamps();

View File

@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('branches', function (Blueprint $table) {
$table->unsignedBigInteger('parent_id')->nullable()->after('name');
$table->foreign('parent_id')->references('id')->on('branches')->onDelete('set null');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('branches', function (Blueprint $table) {
$table->dropForeign(['parent_id']);
$table->dropColumn('parent_id');
});
}
};

View File

@@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('branches', function (Blueprint $table) {
$table->string('address')->nullable()->after('name');
$table->string('mnemonic')->nullable()->after('address');
$table->string('customer_company')->nullable()->after('mnemonic');
$table->string('customer_mnemonic')->nullable()->after('customer_company');
$table->string('company_group')->nullable()->after('customer_mnemonic');
$table->string('curr_no')->nullable()->after('company_group');
$table->string('co_code')->nullable()->after('curr_no');
$table->boolean('l_vendor_atm')->default(false)->after('co_code');
$table->boolean('l_vendor_cpc')->default(false)->after('l_vendor_atm');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('branches', function (Blueprint $table) {
$table->dropColumn([
'address',
'mnemonic',
'customer_company',
'customer_mnemonic',
'company_group',
'curr_no',
'co_code',
'l_vendor_atm',
'l_vendor_cpc'
]);
});
}
};

View File

@@ -11,6 +11,11 @@ class BasicdataDatabaseSeeder extends Seeder
*/ */
public function run(): void public function run(): void
{ {
// $this->call([]); $this->call([
PermissionSeeder::class,
BranchesSeeder::class,
CurrencySeeder::class,
HolidayCalendarSeeder::class
]);
} }
} }

View File

@@ -0,0 +1,856 @@
<?php
namespace Modules\Basicdata\database\seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
class BranchesSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$now = Carbon::now();
$branches = [
[
'code' => 'ID0010001',
'name' => 'PT. Bank Artha Graha',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010002',
'name' => 'SURYOPRANOTO - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010003',
'name' => 'PURI INDAH - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010005',
'name' => 'ARTHA GADING - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010006',
'name' => 'MANGGA DUA PLAZA',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010008',
'name' => 'KPO SUDIRMAN',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010009',
'name' => 'MELAWAI - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010010',
'name' => 'SUNTER - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010011',
'name' => 'TANAH ABANG - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010012',
'name' => 'BINTARO - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010015',
'name' => 'MANGGA BESAR - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010016',
'name' => 'BOROBUDUR - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010017',
'name' => 'TANGERANG - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010018',
'name' => 'BURSA EFEK IND - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010020',
'name' => 'P. JAYAKARTA - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010021',
'name' => 'JATINEGARA - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010022',
'name' => 'CINERE - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010023',
'name' => 'MITRA - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010025',
'name' => 'KLP GADING BLVD - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010026',
'name' => 'MG DUA PSR PAGI - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010027',
'name' => 'RAWAMANGUN - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010028',
'name' => 'KWITANG',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010029',
'name' => 'COKROAMINOTO - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010030',
'name' => 'ASIA AFRIKA BDG - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010031',
'name' => 'BKR Bandung - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010032',
'name' => 'SETIABUDI BDG - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010033',
'name' => 'PAJAJARAN-KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010035',
'name' => 'BUAH BATU BDG - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010036',
'name' => 'RAYA SUDIRMAN BDG -KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010037',
'name' => 'GARUT - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010038',
'name' => 'RAJAWALI - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010039',
'name' => 'Cimahi - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010050',
'name' => 'Ir.Soekarno(MERR)-KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010051',
'name' => 'PASAR ATUM SBY - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010052',
'name' => 'SURABAYA KARET -KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010053',
'name' => 'KEDUNGDORO - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010055',
'name' => 'HR MUHAMMAD SBY - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010056',
'name' => 'PRAPEN - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010057',
'name' => 'SIDOARJO KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010058',
'name' => 'PASAR TURI - KK',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010060',
'name' => 'RENON DENPASAR - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010061',
'name' => 'KUTA BALI - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010062',
'name' => 'SUNSET ROAD - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010063',
'name' => 'Diponegoro Denpasar KK',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010070',
'name' => 'PEMUDA MEDAN - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010071',
'name' => 'CEMARA ASRI MDN - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010072',
'name' => 'SUTOMO MEDAN - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010073',
'name' => 'JL CIREBON MDN - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010075',
'name' => 'ASIA MEDAN - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010080',
'name' => 'KARTINI - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010081',
'name' => 'SURYANEGARA CRB - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010082',
'name' => 'PLERED CRB - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010090',
'name' => 'MATRAMAN-KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010091',
'name' => 'CEMPAKA PUTIH - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010092',
'name' => 'CIKARANG - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010093',
'name' => 'ROXY MAS - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010095',
'name' => 'SAWAH BESAR - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010096',
'name' => 'GREENVILLE-KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010097',
'name' => 'KEBUN JERUK - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010098',
'name' => 'KOPI - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010099',
'name' => 'PANTAI IND KAPUK - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010100',
'name' => 'PLUIT KENCANA - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010101',
'name' => 'MANGGA DUA SQUARE-KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010102',
'name' => 'CIRACAS - KK',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010103',
'name' => 'DEPOK - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010105',
'name' => 'UJUNG MENTENG-KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010106',
'name' => 'CURUG-KK',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010107',
'name' => 'BSD - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010108',
'name' => 'Tzu Chi - KK',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010109',
'name' => 'KARAWANG - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010110',
'name' => 'PANDANARAN SMG - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010111',
'name' => 'GANG BESEN - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010112',
'name' => 'PEMUDA - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010120',
'name' => 'SAMRATULANGI MAN - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010121',
'name' => 'MANADO MALL - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010122',
'name' => 'CALACA MANADO - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010130',
'name' => 'MAKASSAR - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010131',
'name' => 'Veteran Makassar - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010132',
'name' => 'RATULANGI - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010150',
'name' => 'WATAMPONE - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010160',
'name' => 'BITUNG - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010170',
'name' => 'DIPONEGORO AMBON - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010171',
'name' => 'MARDIKA AMBON - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010180',
'name' => 'TERNATE - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010190',
'name' => 'KENDARI-KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010191',
'name' => 'RAROWATU BOMBANA - KK',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010192',
'name' => 'ANGATA KONAWE SLT - KK',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010200',
'name' => 'LAMPUNG-KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010201',
'name' => 'PEMUDA LAMPUNG - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010210',
'name' => 'KUPANG - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010220',
'name' => 'BATAM - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010221',
'name' => 'BATAM CENTER - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010230',
'name' => 'SAMARINDA-KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010231',
'name' => 'BERAU KALTIM - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010250',
'name' => 'BOGOR-KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010251',
'name' => 'CIPANAS-KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010260',
'name' => 'SOLO - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010270',
'name' => 'GADING SERPONG - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010272',
'name' => 'Setiabudi Kuningan KK',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010273',
'name' => 'TAMAN PALEM - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010275',
'name' => 'APART CITY HOME - KK',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010276',
'name' => 'TEBET - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010277',
'name' => 'CIPULIR - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010278',
'name' => 'PONDOK INDAH - KK',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010279',
'name' => 'WILADATIKA - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010280',
'name' => 'JABABEKA KK',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010281',
'name' => 'PEKANBARU - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010282',
'name' => 'PALEMBANG-KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010283',
'name' => 'RIAU PEKAN BARU - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010290',
'name' => 'SAYANGAN PLB - KCP',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010300',
'name' => 'PONTIANAK - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010310',
'name' => 'BANJARMASIN - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010320',
'name' => 'JAMBI - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010330',
'name' => 'PANGKAL PINANG - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010350',
'name' => 'BALIKPAPAN - KC',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010380',
'name' => 'PLAZA II INDAH - KK',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010381',
'name' => 'CIPUTAT - KK',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010383',
'name' => 'JEMBATAN LIMA - KK',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
[
'code' => 'ID0010385',
'name' => 'DAAN MOGOT - KK',
'status' => true,
'created_at' => $now,
'updated_at' => $now
],
];
DB::table('branches')->insert($branches);
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Modules\Basicdata\Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
class CurrencySeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$now = Carbon::now();
$currencies = [
['code' => 'USD', 'name' => 'United States Dollar', 'symbol' => '$', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'EUR', 'name' => 'Euro', 'symbol' => '€', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'GBP', 'name' => 'British Pound Sterling', 'symbol' => '£', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'JPY', 'name' => 'Japanese Yen', 'symbol' => '¥', 'decimal_places' => 0, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'AUD', 'name' => 'Australian Dollar', 'symbol' => 'A$', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'CAD', 'name' => 'Canadian Dollar', 'symbol' => 'C$', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'CHF', 'name' => 'Swiss Franc', 'symbol' => 'CHF', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'CNY', 'name' => 'Chinese Yuan', 'symbol' => '¥', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'SEK', 'name' => 'Swedish Krona', 'symbol' => 'kr', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'NZD', 'name' => 'New Zealand Dollar', 'symbol' => 'NZ$', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'MXN', 'name' => 'Mexican Peso', 'symbol' => '$', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'SGD', 'name' => 'Singapore Dollar', 'symbol' => 'S$', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'HKD', 'name' => 'Hong Kong Dollar', 'symbol' => 'HK$', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'NOK', 'name' => 'Norwegian Krone', 'symbol' => 'kr', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'KRW', 'name' => 'South Korean Won', 'symbol' => '₩', 'decimal_places' => 0, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'TRY', 'name' => 'Turkish Lira', 'symbol' => '₺', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'RUB', 'name' => 'Russian Ruble', 'symbol' => '₽', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'INR', 'name' => 'Indian Rupee', 'symbol' => '₹', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'BRL', 'name' => 'Brazilian Real', 'symbol' => 'R$', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'ZAR', 'name' => 'South African Rand', 'symbol' => 'R', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'IDR', 'name' => 'Indonesian Rupiah', 'symbol' => 'Rp', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'MYR', 'name' => 'Malaysian Ringgit', 'symbol' => 'RM', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'PHP', 'name' => 'Philippine Peso', 'symbol' => '₱', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'THB', 'name' => 'Thai Baht', 'symbol' => '฿', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'PLN', 'name' => 'Polish Złoty', 'symbol' => 'zł', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'DKK', 'name' => 'Danish Krone', 'symbol' => 'kr', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'AED', 'name' => 'United Arab Emirates Dirham', 'symbol' => 'د.إ', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'ARS', 'name' => 'Argentine Peso', 'symbol' => '$', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'CLP', 'name' => 'Chilean Peso', 'symbol' => '$', 'decimal_places' => 0, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'EGP', 'name' => 'Egyptian Pound', 'symbol' => 'E£', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'KWD', 'name' => 'Kuwaiti Dinar', 'symbol' => 'د.ك', 'decimal_places' => 3, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'NGN', 'name' => 'Nigerian Naira', 'symbol' => '₦', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'QAR', 'name' => 'Qatari Riyal', 'symbol' => 'ر.ق', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'SAR', 'name' => 'Saudi Riyal', 'symbol' => 'ر.س', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'VND', 'name' => 'Vietnamese Dong', 'symbol' => '₫', 'decimal_places' => 0, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'BDT', 'name' => 'Bangladeshi Taka', 'symbol' => '৳', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'ILS', 'name' => 'Israeli New Shekel', 'symbol' => '₪', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'COP', 'name' => 'Colombian Peso', 'symbol' => '$', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'PEN', 'name' => 'Peruvian Sol', 'symbol' => 'S/', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
['code' => 'PKR', 'name' => 'Pakistani Rupee', 'symbol' => '₨', 'decimal_places' => 2, 'status' => true, 'created_at' => $now, 'updated_at' => $now],
];
DB::table('currencies')->truncate();
DB::table('currencies')->insert($currencies);
}
}

View File

@@ -0,0 +1,216 @@
<?php
namespace Modules\Basicdata\Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
class HolidayCalendarSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$now = Carbon::now();
$holidays = [
// Libur Nasional 2025
[
'date' => '2025-01-01',
'description' => 'Tahun Baru 2025 Masehi',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-01-27',
'description' => 'Isra Miraj Nabi Muhammad SAW',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-01-29',
'description' => 'Tahun Baru Imlek 2576 Kongzili',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-03-29',
'description' => 'Hari Suci Nyepi (Tahun Baru Saka 1947)',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-03-31',
'description' => 'Idul Fitri 1446 Hijriah',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-04-01',
'description' => 'Idul Fitri 1446 Hijriah',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-04-18',
'description' => 'Wafat Yesus Kristus',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-04-20',
'description' => 'Kebangkitan Yesus Kristus (Paskah)',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-05-01',
'description' => 'Hari Buruh Internasional',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-05-12',
'description' => 'Hari Raya Waisak 2569 BE',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-05-29',
'description' => 'Kenaikan Yesus Kristus',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-06-01',
'description' => 'Hari Lahir Pancasila',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-06-06',
'description' => 'Idul Adha 1446 Hijriah',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-06-27',
'description' => '1 Muharam Tahun Baru Islam 1447 Hijriah',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-08-17',
'description' => 'Proklamasi Kemerdekaan',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-09-05',
'description' => 'Maulid Nabi Muhammad SAW',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-12-25',
'description' => 'Kelahiran Yesus Kristus',
'type' => 'national_holiday',
'created_at' => $now,
'updated_at' => $now
],
// Cuti Bersama 2025
[
'date' => '2025-01-28',
'description' => 'Cuti Bersama Tahun Baru Imlek 2576 Kongzili',
'type' => 'collective_leave',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-03-28',
'description' => 'Cuti Bersama Hari Suci Nyepi (Tahun Baru Saka 1947)',
'type' => 'collective_leave',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-04-02',
'description' => 'Cuti Bersama Idul Fitri',
'type' => 'collective_leave',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-04-03',
'description' => 'Cuti Bersama Idul Fitri',
'type' => 'collective_leave',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-04-04',
'description' => 'Cuti Bersama Idul Fitri',
'type' => 'collective_leave',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-04-07',
'description' => 'Cuti Bersama Idul Fitri',
'type' => 'collective_leave',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-05-13',
'description' => 'Cuti Bersama Hari Raya Waisak 2569 BE',
'type' => 'collective_leave',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-05-30',
'description' => 'Cuti Bersama Kenaikan Yesus Kristus',
'type' => 'collective_leave',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-06-09',
'description' => 'Cuti Bersama Idul Adha 1446 Hijriah',
'type' => 'collective_leave',
'created_at' => $now,
'updated_at' => $now
],
[
'date' => '2025-12-26',
'description' => 'Cuti Bersama Kelahiran Yesus Kristus',
'type' => 'collective_leave',
'created_at' => $now,
'updated_at' => $now
],
];
DB::table('holiday_calendars')->insert($holidays);
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Modules\Basicdata\Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Str;
use Modules\Usermanagement\Models\PermissionGroup;
class PermissionSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run()
{
$data = $this->data();
foreach ($data as $value) {
PermissionGroup::updateOrCreate([
'name' => $value['name'],
'slug' => Str::slug($value['name'])
]);
}
}
public function data()
{
return [
['name' => 'basic-data']
];
}
}

View File

@@ -8,7 +8,10 @@
"providers": [ "providers": [
"Modules\\Basicdata\\Providers\\BasicdataServiceProvider" "Modules\\Basicdata\\Providers\\BasicdataServiceProvider"
], ],
"files": [], "files": [
"app/Helpers/Currency.php",
"app/Helpers/HolidayCalendar.php"
],
"menu": { "menu": {
"main": [], "main": [],
"master": [ "master": [

View File

@@ -46,10 +46,40 @@
@enderror @enderror
</div> </div>
</div> </div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Parent Branch
</label>
<div class="flex flex-wrap items-baseline w-full">
<select class="input @error('parent_id') border-danger bg-danger-light @enderror" name="parent_id">
<option value="">-- Select Parent Branch --</option>
@foreach($branches as $parentBranch)
@if(!isset($branch->id) || $parentBranch->id != $branch->id)
<option value="{{ $parentBranch->id }}" {{ (isset($branch->parent_id) && $branch->parent_id == $parentBranch->id) ? 'selected' : '' }}>
{{ $parentBranch->code }} - {{ $parentBranch->name }}
</option>
@endif
@endforeach
</select>
@error('parent_id')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex justify-end"> <div class="flex justify-end">
@if(isset($branch->id))
@can('basic-data.update')
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
Save Save
</button> </button>
@endcan
@else
@can('basic-data.create')
<button type="submit" class="btn btn-primary">
Save
</button>
@endcan
@endif
</div> </div>
</div> </div>
</div> </div>

View File

@@ -12,16 +12,28 @@
Daftar Cabang Daftar Cabang
</h3> </h3>
<div class="flex flex-wrap gap-2 lg:gap-5"> <div class="flex flex-wrap gap-2 lg:gap-5">
<div class="flex"> <div class="flex gap-2 lg:gap-5">
<label class="input input-sm"> <i class="ki-filled ki-magnifier"> </i> <label class="input input-sm"> <i class="ki-filled ki-magnifier"> </i>
<input placeholder="Search Branch" id="search" type="text" value=""> <input placeholder="Search Branch" id="search" type="text" value="">
</label> </label>
<select class="select select-sm" id="parent-filter" data-datatable-filter-column="parent_id">
<option value="">All Parent Branches</option>
@foreach(\Modules\Basicdata\Models\Branch::orderBy('name')->get() as $branch)
<option value="{{ $branch->id }}">{{ $branch->name }}</option>
@endforeach
</select>
</div> </div>
<div class="flex flex-wrap gap-2.5"> <div class="flex flex-wrap gap-2 lg:gap-5">
<div class="h-[24px] border border-r-gray-200"></div> <div class="h-[24px] border border-r-gray-200"></div>
<a class="btn btn-sm btn-light" href="{{ route('basicdata.branch.export') }}"> Export to Excel </a> @can('basic-data.export')
<a id="export-btn" class="btn btn-sm btn-light" href="{{ route('basicdata.branch.export') }}"> Export to Excel </a>
@endcan
@can('basic-data.create')
<a class="btn btn-sm btn-primary" href="{{ route('basicdata.branch.create') }}"> Tambah Cabang </a> <a class="btn btn-sm btn-primary" href="{{ route('basicdata.branch.create') }}"> Tambah Cabang </a>
@endcan
@can('basic-data.delete')
<button class="btn btn-sm btn-danger hidden" id="deleteSelected" onclick="deleteSelectedRows()">Delete Selected</button> <button class="btn btn-sm btn-danger hidden" id="deleteSelected" onclick="deleteSelectedRows()">Delete Selected</button>
@endcan
</div> </div>
</div> </div>
</div> </div>
@@ -41,6 +53,14 @@
<span class="sort"> <span class="sort-label"> Cabang </span> <span class="sort"> <span class="sort-label"> Cabang </span>
<span class="sort-icon"> </span> </span> <span class="sort-icon"> </span> </span>
</th> </th>
<th class="min-w-[250px]" data-datatable-column="name">
<span class="sort"> <span class="sort-label"> Cabang Induk</span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[250px]" data-datatable-column="address">
<span class="sort"> <span class="sort-label"> Address</span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[50px] text-center" data-datatable-column="actions">Action</th> <th class="min-w-[50px] text-center" data-datatable-column="actions">Action</th>
</tr> </tr>
</thead> </thead>
@@ -84,7 +104,7 @@
$.ajax(`basic-data/cabang/${data}`, { $.ajax(`basic-data/cabang/${data}`, {
type: 'DELETE' type: 'DELETE'
}).then((response) => { }).then((response) => {
swal.fire('Deleted!', 'User has been deleted.', 'success').then(() => { swal.fire('Deleted!', 'Branch has been deleted.', 'success').then(() => {
window.location.reload(); window.location.reload();
}); });
}).catch((error) => { }).catch((error) => {
@@ -139,7 +159,9 @@
<script type="module"> <script type="module">
const element = document.querySelector('#branch-table'); const element = document.querySelector('#branch-table');
const searchInput = document.getElementById('search'); const searchInput = document.getElementById('search');
const parentFilter = document.getElementById('parent-filter');
const deleteSelectedButton = document.getElementById('deleteSelected'); const deleteSelectedButton = document.getElementById('deleteSelected');
const exportBtn = document.getElementById('export-btn');
const apiUrl = element.getAttribute('data-api-url'); const apiUrl = element.getAttribute('data-api-url');
const dataTableOptions = { const dataTableOptions = {
@@ -162,29 +184,92 @@
name: { name: {
title: 'Cabang', title: 'Cabang',
}, },
parent_id: {
title: 'Cabang Induk',
},
address: {
title: 'Address',
},
actions: { actions: {
title: 'Status', title: 'Status',
render: (item, data) => { render: (item, data) => {
return `<div class="flex flex-nowrap justify-center"> let html = `<div class="flex flex-nowrap justify-center">`;
<a class="btn btn-sm btn-icon btn-clear btn-info" href="basic-data/cabang/${data.id}/edit">
@can('basic-data.update')
html += `<a class="btn btn-sm btn-icon btn-clear btn-info" href="basic-data/cabang/${data.id}/edit">
<i class="ki-outline ki-notepad-edit"></i> <i class="ki-outline ki-notepad-edit"></i>
</a> </a>`;
<a onclick="deleteData(${data.id})" class="delete btn btn-sm btn-icon btn-clear btn-danger"> @endcan
@can('basic-data.delete')
html += `<a onclick="deleteData(${data.id})" class="delete btn btn-sm btn-icon btn-clear btn-danger">
<i class="ki-outline ki-trash"></i> <i class="ki-outline ki-trash"></i>
</a> </a>`;
</div>`; @endcan
html += `</div>`;
return html;
}, },
} }
}, },
}; };
let dataTable = new KTDataTable(element, dataTableOptions); let dataTable = new KTDataTable(element, dataTableOptions);
// Function to apply all filters
function applyFilters() {
let filters = {};
if (searchInput.value) {
filters.search = searchInput.value;
}
if (parentFilter.value) {
filters.parent_id = parentFilter.value;
}
dataTable.search(filters);
}
// Update export URL with filters
function updateExportUrl() {
let url = new URL(exportBtn.href);
if (parentFilter.value) {
url.searchParams.set('parent_id', parentFilter.value);
} else {
url.searchParams.delete('parent_id');
}
if (searchInput.value) {
url.searchParams.set('search', searchInput.value);
} else {
url.searchParams.delete('search');
}
exportBtn.href = url.toString();
}
// Custom search functionality // Custom search functionality
searchInput.addEventListener('input', function () { searchInput.addEventListener('input', function () {
const searchValue = this.value.trim(); dataTable.goPage(1);
dataTable.search(searchValue, true);
// Update export URL with search parameter
applyFilters();
updateExportUrl();
}); });
// Parent branch filter functionality
parentFilter.addEventListener('change', function() {
updateExportUrl();
applyFilters();
});
exportBtn.addEventListener('click', function() {
updateExportUrl();
applyFilters();
})
function updateDeleteButtonVisibility() { function updateDeleteButtonVisibility() {
const selectedCheckboxes = document.querySelectorAll('input[data-datatable-row-check="true"]:checked'); const selectedCheckboxes = document.querySelectorAll('input[data-datatable-row-check="true"]:checked');
if (selectedCheckboxes.length > 0) { if (selectedCheckboxes.length > 0) {
@@ -213,4 +298,3 @@
window.dataTable = dataTable; window.dataTable = dataTable;
</script> </script>
@endpush @endpush

View File

@@ -6,14 +6,12 @@
@section('content') @section('content')
<div class="w-full grid gap-5 lg:gap-7.5 mx-auto"> <div class="w-full grid gap-5 lg:gap-7.5 mx-auto">
<form method="POST" action="{{ isset($currency->id) ? route('basicdata.currency.update', $currency->id) : route('basicdata.currency.store') }}">
@csrf
@if(isset($currency->id)) @if(isset($currency->id))
<form action="{{ route('basicdata.currency.update', $currency->id) }}" method="POST">
<input type="hidden" name="id" value="{{ $currency->id }}"> <input type="hidden" name="id" value="{{ $currency->id }}">
@method('PUT') @method('PUT')
@else
<form method="POST" action="{{ route('basicdata.currency.store') }}">
@endif @endif
@csrf
<div class="card pb-2.5"> <div class="card pb-2.5">
<div class="card-header" id="basic_settings"> <div class="card-header" id="basic_settings">
<h3 class="card-title"> <h3 class="card-title">
@@ -46,6 +44,17 @@
@enderror @enderror
</div> </div>
</div> </div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Symbol
</label>
<div class="flex flex-wrap items-baseline w-full">
<input class="input @error('symbol') border-danger bg-danger-light @enderror" type="text" name="symbol" value="{{ $currency->symbol ?? '' }}">
@error('symbol')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5"> <div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56"> <label class="form-label max-w-56">
Decimal Places Decimal Places
@@ -58,9 +67,19 @@
</div> </div>
</div> </div>
<div class="flex justify-end"> <div class="flex justify-end">
@if(isset($currency->id))
@can('basic-data.update')
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
Save Save
</button> </button>
@endcan
@else
@can('basic-data.create')
<button type="submit" class="btn btn-primary">
Save
</button>
@endcan
@endif
</div> </div>
</div> </div>
</div> </div>

View File

@@ -19,9 +19,15 @@
</div> </div>
<div class="flex flex-wrap gap-2.5"> <div class="flex flex-wrap gap-2.5">
<div class="h-[24px] border border-r-gray-200"></div> <div class="h-[24px] border border-r-gray-200"></div>
<a class="btn btn-sm btn-light" href="{{ route('basicdata.currency.export') }}"> Export to Excel </a> @can('basic-data.export')
<a id="export-btn" class="btn btn-sm btn-light" href="{{ route('basicdata.currency.export') }}"> Export to Excel </a>
@endcan
@can('basic-data.create')
<a class="btn btn-sm btn-primary" href="{{ route('basicdata.currency.create') }}"> Tambah Mata Uang </a> <a class="btn btn-sm btn-primary" href="{{ route('basicdata.currency.create') }}"> Tambah Mata Uang </a>
@endcan
@can('basic-data.delete')
<button class="btn btn-sm btn-danger hidden" id="deleteSelected" onclick="deleteSelectedRows()">Delete Selected</button> <button class="btn btn-sm btn-danger hidden" id="deleteSelected" onclick="deleteSelectedRows()">Delete Selected</button>
@endcan
</div> </div>
</div> </div>
</div> </div>
@@ -41,6 +47,10 @@
<span class="sort"> <span class="sort-label"> Mata Uang </span> <span class="sort"> <span class="sort-label"> Mata Uang </span>
<span class="sort-icon"> </span> </span> <span class="sort-icon"> </span> </span>
</th> </th>
<th class="min-w-[150px]" data-datatable-column="symbol">
<span class="sort"> <span class="sort-label"> Symbol </span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[50px]" data-datatable-column="decimal_places"> <th class="min-w-[50px]" data-datatable-column="decimal_places">
<span class="sort"> <span class="sort-label"> Decimal Places </span> <span class="sort"> <span class="sort-label"> Decimal Places </span>
<span class="sort-icon"> </span> </span> <span class="sort-icon"> </span> </span>
@@ -142,6 +152,7 @@
<script type="module"> <script type="module">
const element = document.querySelector('#currency-table'); const element = document.querySelector('#currency-table');
const searchInput = document.getElementById('search'); const searchInput = document.getElementById('search');
const exportBtn = document.getElementById('export-btn');
const deleteSelectedButton = document.getElementById('deleteSelected'); const deleteSelectedButton = document.getElementById('deleteSelected');
const apiUrl = element.getAttribute('data-api-url'); const apiUrl = element.getAttribute('data-api-url');
@@ -165,30 +176,59 @@
name: { name: {
title: 'Mata Uang', title: 'Mata Uang',
}, },
symbol: {
title: 'Symbol',
},
decimal_places: { decimal_places: {
title: 'Decimal Places', title: 'Decimal Places',
}, },
actions: { actions: {
title: 'Status', title: 'Status',
render: (item, data) => { render: (item, data) => {
return `<div class="flex flex-nowrap justify-center"> let html = `<div class="flex flex-nowrap justify-center">`;
<a class="btn btn-sm btn-icon btn-clear btn-info" href="basic-data/mata-uang/${data.id}/edit">
@can('basic-data.update')
html += `<a class="btn btn-sm btn-icon btn-clear btn-info" href="basic-data/mata-uang/${data.id}/edit">
<i class="ki-outline ki-notepad-edit"></i> <i class="ki-outline ki-notepad-edit"></i>
</a> </a>`;
<a onclick="deleteData(${data.id})" class="delete btn btn-sm btn-icon btn-clear btn-danger"> @endcan
@can('basic-data.delete')
html += `<a onclick="deleteData(${data.id})" class="delete btn btn-sm btn-icon btn-clear btn-danger">
<i class="ki-outline ki-trash"></i> <i class="ki-outline ki-trash"></i>
</a> </a>`;
</div>`; @endcan
html += `</div>`;
return html;
}, },
} }
}, },
}; };
let dataTable = new KTDataTable(element, dataTableOptions); let dataTable = new KTDataTable(element, dataTableOptions);
// Update export URL with filters
function updateExportUrl() {
let url = new URL(exportBtn.href);
if (searchInput.value) {
url.searchParams.set('search', searchInput.value);
} else {
url.searchParams.delete('search');
}
exportBtn.href = url.toString();
}
// Custom search functionality // Custom search functionality
searchInput.addEventListener('input', function () { searchInput.addEventListener('input', function () {
const searchValue = this.value.trim(); const searchValue = this.value.trim();
dataTable.goPage(1);
dataTable.search(searchValue, true); dataTable.search(searchValue, true);
// Update export URL with search parameter
updateExportUrl();
}); });
function updateDeleteButtonVisibility() { function updateDeleteButtonVisibility() {
@@ -219,4 +259,3 @@
window.dataTable = dataTable; window.dataTable = dataTable;
</script> </script>
@endpush @endpush

View File

@@ -6,14 +6,12 @@
@section('content') @section('content')
<div class="w-full grid gap-5 lg:gap-7.5 mx-auto"> <div class="w-full grid gap-5 lg:gap-7.5 mx-auto">
<form method="POST" action="{{ isset($holiday->id) ? route('basicdata.holidaycalendar.update', $holiday->id) : route('basicdata.holidaycalendar.store') }}">
@csrf
@if(isset($holiday->id)) @if(isset($holiday->id))
<form action="{{ route('basicdata.holidaycalendar.update', $holiday->id) }}" method="POST">
<input type="hidden" name="id" value="{{ $holiday->id }}"> <input type="hidden" name="id" value="{{ $holiday->id }}">
@method('PUT') @method('PUT')
@else
<form method="POST" action="{{ route('basicdata.holidaycalendar.store') }}">
@endif @endif
@csrf
<div class="card pb-2.5"> <div class="card pb-2.5">
<div class="card-header" id="basic_settings"> <div class="card-header" id="basic_settings">
<h3 class="card-title"> <h3 class="card-title">
@@ -65,9 +63,19 @@
</div> </div>
</div> </div>
<div class="flex justify-end"> <div class="flex justify-end">
@if(isset($holiday->id))
@can('basic-data.update')
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
Save Save
</button> </button>
@endcan
@else
@can('basic-data.create')
<button type="submit" class="btn btn-primary">
Save
</button>
@endcan
@endif
</div> </div>
</div> </div>
</div> </div>

View File

@@ -19,9 +19,15 @@
</div> </div>
<div class="flex flex-wrap gap-2.5"> <div class="flex flex-wrap gap-2.5">
<div class="h-[24px] border border-r-gray-200"></div> <div class="h-[24px] border border-r-gray-200"></div>
<a class="btn btn-sm btn-light" href="{{ route('basicdata.holidaycalendar.export') }}"> Export to Excel </a> @can('basic-data.export')
<a id="export-btn" class="btn btn-sm btn-light" href="{{ route('basicdata.holidaycalendar.export') }}"> Export to Excel </a>
@endcan
@can('basic-data.create')
<a class="btn btn-sm btn-primary" href="{{ route('basicdata.holidaycalendar.create') }}"> Tambah Hari Libur </a> <a class="btn btn-sm btn-primary" href="{{ route('basicdata.holidaycalendar.create') }}"> Tambah Hari Libur </a>
@endcan
@can('basic-data.delete')
<button class="btn btn-sm btn-danger hidden" id="deleteSelected" onclick="deleteSelectedRows()">Delete Selected</button> <button class="btn btn-sm btn-danger hidden" id="deleteSelected" onclick="deleteSelectedRows()">Delete Selected</button>
@endcan
</div> </div>
</div> </div>
</div> </div>
@@ -143,6 +149,7 @@
const element = document.querySelector('#holiday-calendar-table'); const element = document.querySelector('#holiday-calendar-table');
const searchInput = document.getElementById('search'); const searchInput = document.getElementById('search');
const deleteSelectedButton = document.getElementById('deleteSelected'); const deleteSelectedButton = document.getElementById('deleteSelected');
const exportBtn = document.getElementById('export-btn');
const apiUrl = element.getAttribute('data-api-url'); const apiUrl = element.getAttribute('data-api-url');
const dataTableOptions = { const dataTableOptions = {
@@ -177,24 +184,50 @@
actions: { actions: {
title: 'Action', title: 'Action',
render: (item, data) => { render: (item, data) => {
return `<div class="flex flex-nowrap justify-center"> let html = `<div class="flex flex-nowrap justify-center">`;
<a class="btn btn-sm btn-icon btn-clear btn-info" href="basic-data/holidaycalendar/${data.id}/edit">
@can('basic-data.update')
html += `<a class="btn btn-sm btn-icon btn-clear btn-info" href="basic-data/holidaycalendar/${data.id}/edit">
<i class="ki-outline ki-notepad-edit"></i> <i class="ki-outline ki-notepad-edit"></i>
</a> </a>`;
<a onclick="deleteData(${data.id})" class="delete btn btn-sm btn-icon btn-clear btn-danger"> @endcan
@can('basic-data.delete')
html += `<a onclick="deleteData(${data.id})" class="delete btn btn-sm btn-icon btn-clear btn-danger">
<i class="ki-outline ki-trash"></i> <i class="ki-outline ki-trash"></i>
</a> </a>`;
</div>`; @endcan
html += `</div>`;
return html;
}, },
} }
}, },
}; };
let dataTable = new KTDataTable(element, dataTableOptions); let dataTable = new KTDataTable(element, dataTableOptions);
// Update export URL with filters
function updateExportUrl() {
let url = new URL(exportBtn.href);
if (searchInput.value) {
url.searchParams.set('search', searchInput.value);
} else {
url.searchParams.delete('search');
}
exportBtn.href = url.toString();
}
// Custom search functionality // Custom search functionality
searchInput.addEventListener('input', function () { searchInput.addEventListener('input', function () {
const searchValue = this.value.trim(); const searchValue = this.value.trim();
dataTable.goPage(1);
dataTable.search(searchValue, true); dataTable.search(searchValue, true);
// Update export URL with search parameter
updateExportUrl();
}); });
function updateDeleteButtonVisibility() { function updateDeleteButtonVisibility() {

View File

@@ -19,7 +19,6 @@
Route::middleware(['auth'])->group(function () { Route::middleware(['auth'])->group(function () {
Route::name('basicdata.')->prefix('basic-data')->group(function () { Route::name('basicdata.')->prefix('basic-data')->group(function () {
Route::name('currency.')->prefix('mata-uang')->group(function () { Route::name('currency.')->prefix('mata-uang')->group(function () {
Route::get('restore/{id}', [CurrencyController::class, 'restore'])->name('restore');
Route::get('datatables', [CurrencyController::class, 'dataForDatatables'])->name('datatables'); Route::get('datatables', [CurrencyController::class, 'dataForDatatables'])->name('datatables');
Route::get('export', [CurrencyController::class, 'export'])->name('export'); Route::get('export', [CurrencyController::class, 'export'])->name('export');
Route::post('delete-multiple', [CurrencyController::class, 'deleteMultiple'])->name('deleteMultiple'); Route::post('delete-multiple', [CurrencyController::class, 'deleteMultiple'])->name('deleteMultiple');
@@ -39,7 +38,6 @@
Route::name('branch.')->prefix('cabang')->group(function () { Route::name('branch.')->prefix('cabang')->group(function () {
Route::get('restore/{id}', [BranchController::class, 'restore'])->name('restore');
Route::get('datatables', [BranchController::class, 'dataForDatatables'])->name('datatables'); Route::get('datatables', [BranchController::class, 'dataForDatatables'])->name('datatables');
Route::get('export', [BranchController::class, 'export'])->name('export'); Route::get('export', [BranchController::class, 'export'])->name('export');
Route::post('delete-multiple', [BranchController::class, 'deleteMultiple'])->name('deleteMultiple'); Route::post('delete-multiple', [BranchController::class, 'deleteMultiple'])->name('deleteMultiple');
@@ -58,15 +56,10 @@
]); ]);
Route::group(['prefix' => 'holidaycalendar', 'as' => 'holidaycalendar.'], function () { Route::group(['prefix' => 'holidaycalendar', 'as' => 'holidaycalendar.'], function () {
Route::get('/', [HolidayCalendarController::class, 'index'])->name('index');
Route::get('/create', [HolidayCalendarController::class, 'create'])->name('create');
Route::post('/', [HolidayCalendarController::class, 'store'])->name('store');
Route::get('/{id}/edit', [HolidayCalendarController::class, 'edit'])->name('edit');
Route::put('/{id}', [HolidayCalendarController::class, 'update'])->name('update');
Route::delete('/{id}', [HolidayCalendarController::class, 'destroy'])->name('destroy');
Route::get('/datatables', [HolidayCalendarController::class, 'dataForDatatables'])->name('datatables'); Route::get('/datatables', [HolidayCalendarController::class, 'dataForDatatables'])->name('datatables');
Route::get('/export', [HolidayCalendarController::class, 'export'])->name('export'); Route::get('/export', [HolidayCalendarController::class, 'export'])->name('export');
Route::post('delete-multiple', [HolidayCalendarController::class, 'deleteMultiple'])->name('deleteMultiple'); Route::post('delete-multiple', [HolidayCalendarController::class, 'deleteMultiple'])->name('deleteMultiple');
}); });
Route::resource('holidaycalendar', HolidayCalendarController::class);
}); });
}); });

View File

@@ -0,0 +1,478 @@
<?php
namespace Modules\Basicdata\Tests\Feature;
use Tests\TestCase;
use Modules\Basicdata\Models\Branch;
use Modules\Usermanagement\Models\User;
use Modules\Usermanagement\Models\Role;
use Modules\Usermanagement\Models\Permission;
use Modules\Usermanagement\Models\PermissionGroup;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
class BranchControllerTest extends TestCase
{
use RefreshDatabase;
protected $user;
protected $adminRole;
protected $branch;
protected $parentBranch;
protected function setUp(): void
{
parent::setUp();
// Create permission group first
$permissionGroup = PermissionGroup::create([
'name' => 'basic-data',
'slug' => 'basic-data'
]);
// Create permissions with permission_group_id
Permission::create([
'name' => 'basic-data.create',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
Permission::create([
'name' => 'basic-data.read',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
Permission::create([
'name' => 'basic-data.update',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
Permission::create([
'name' => 'basic-data.delete',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
Permission::create([
'name' => 'basic-data.export',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
// Create admin role with all permissions
$this->adminRole = Role::create(['name' => 'admin', 'guard_name' => 'web']);
$this->adminRole->givePermissionTo(Permission::all());
// Create a user with admin role
$this->user = User::factory()->create();
$this->user->assignRole($this->adminRole);
// Create a parent branch for testing
$this->parentBranch = Branch::create([
'code' => 'PARENT',
'name' => 'Parent Branch'
]);
// Create a branch for testing
$this->branch = Branch::create([
'code' => 'TEST',
'name' => 'Test Branch'
]);
}
#[Test]
public function user_with_permission_can_view_branches_index()
{
$response = $this->actingAs($this->user)
->get(route('basicdata.branch.index'));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_view_branches_index()
{
// Create a role without permissions
$role = Role::create(['name' => 'viewer', 'guard_name' => 'web']);
// Create a user with the viewer role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('basicdata.branch.index'));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_create_branch()
{
$response = $this->actingAs($this->user)
->get(route('basicdata.branch.create'));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_create_branch()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('basicdata.branch.create'));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_store_branch()
{
$branchData = [
'code' => 'NEW',
'name' => 'New Branch'
];
$response = $this->actingAs($this->user)
->post(route('basicdata.branch.store'), $branchData);
$response->assertRedirect(route('basicdata.branch.index'));
$this->assertDatabaseHas('branches', $branchData);
}
#[Test]
public function user_with_permission_can_store_branch_with_parent()
{
$branchData = [
'code' => 'CHILD',
'name' => 'Child Branch',
'parent_id' => $this->parentBranch->id
];
$response = $this->actingAs($this->user)
->post(route('basicdata.branch.store'), $branchData);
$response->assertRedirect(route('basicdata.branch.index'));
$this->assertDatabaseHas('branches', $branchData);
// Verify the relationship
$childBranch = Branch::where('code', 'CHILD')->first();
$this->assertEquals($this->parentBranch->id, $childBranch->parent_id);
$this->assertTrue($childBranch->parent()->exists());
$this->assertEquals($this->parentBranch->id, $childBranch->parent->id);
}
#[Test]
public function user_without_permission_cannot_store_branch()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$branchData = [
'code' => 'NEW',
'name' => 'New Branch'
];
$response = $this->actingAs($user)
->post(route('basicdata.branch.store'), $branchData);
$response->assertStatus(403);
$this->assertDatabaseMissing('branches', $branchData);
}
#[Test]
public function user_with_permission_can_edit_branch()
{
$response = $this->actingAs($this->user)
->get(route('basicdata.branch.edit', $this->branch->id));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_edit_branch()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('basicdata.branch.edit', $this->branch->id));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_update_branch()
{
$updatedData = [
'code' => 'UPD',
'name' => 'Updated Branch'
];
$response = $this->actingAs($this->user)
->put(route('basicdata.branch.update', $this->branch->id), $updatedData);
$response->assertRedirect(route('basicdata.branch.index'));
$this->assertDatabaseHas('branches', $updatedData);
}
#[Test]
public function user_with_permission_can_update_branch_with_parent()
{
$updatedData = [
'code' => 'UPD',
'name' => 'Updated Branch',
'parent_id' => $this->parentBranch->id
];
$response = $this->actingAs($this->user)
->put(route('basicdata.branch.update', $this->branch->id), $updatedData);
$response->assertRedirect(route('basicdata.branch.index'));
$this->assertDatabaseHas('branches', $updatedData);
// Verify the relationship
$this->branch->refresh();
$this->assertEquals($this->parentBranch->id, $this->branch->parent_id);
$this->assertTrue($this->branch->parent()->exists());
$this->assertEquals($this->parentBranch->id, $this->branch->parent->id);
}
#[Test]
public function user_with_permission_can_remove_parent_from_branch()
{
// First set a parent
$this->branch->update(['parent_id' => $this->parentBranch->id]);
$this->assertEquals($this->parentBranch->id, $this->branch->parent_id);
// Then remove it
$updatedData = [
'code' => 'UPD',
'name' => 'Updated Branch',
'parent_id' => null
];
$response = $this->actingAs($this->user)
->put(route('basicdata.branch.update', $this->branch->id), $updatedData);
$response->assertRedirect(route('basicdata.branch.index'));
// Verify the relationship is removed
$this->branch->refresh();
$this->assertNull($this->branch->parent_id);
$this->assertFalse($this->branch->parent()->exists());
}
#[Test]
public function user_without_permission_cannot_update_branch()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$updatedData = [
'code' => 'UPD',
'name' => 'Updated Branch'
];
$response = $this->actingAs($user)
->put(route('basicdata.branch.update', $this->branch->id), $updatedData);
$response->assertStatus(403);
$this->assertDatabaseMissing('branches', $updatedData);
}
#[Test]
public function user_with_permission_can_delete_branch()
{
$response = $this->actingAs($this->user)
->delete(route('basicdata.branch.destroy', $this->branch->id));
$response->assertJson(['success' => true]);
$this->assertSoftDeleted($this->branch);
}
#[Test]
public function user_without_permission_cannot_delete_branch()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->delete(route('basicdata.branch.destroy', $this->branch->id));
$response->assertStatus(403);
$this->assertDatabaseHas('branches', ['id' => $this->branch->id, 'deleted_at' => null]);
}
#[Test]
public function user_with_permission_can_export_branches()
{
$response = $this->actingAs($this->user)
->get(route('basicdata.branch.export'));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_export_branches()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('basicdata.branch.export'));
$response->assertStatus(403);
}
#[Test]
public function branch_cannot_be_its_own_parent()
{
$updatedData = [
'code' => 'SELF',
'name' => 'Self-Referencing Branch',
'parent_id' => $this->branch->id
];
$response = $this->actingAs($this->user)
->from(route('basicdata.branch.edit', $this->branch->id)) // Tambahkan ini untuk mengetahui URL sebelumnya
->put(route('basicdata.branch.update', $this->branch->id), $updatedData);
// Pastikan redirect back dengan kesalahan
$response->assertRedirect();
$response->assertSessionHasErrors('parent_id');
// Periksa bahwa parent_id tidak berubah
$this->branch->refresh();
$this->assertNull($this->branch->parent_id);
}
#[Test]
public function cannot_delete_branch_with_children()
{
// Create a child branch
$childBranch = Branch::create([
'code' => 'CHILD',
'name' => 'Child Branch',
'parent_id' => $this->parentBranch->id
]);
// Verify the relationship is established
$this->assertEquals($this->parentBranch->id, $childBranch->parent_id);
$this->assertTrue($this->parentBranch->children()->exists());
// Try to delete the parent branch
$response = $this->actingAs($this->user)
->delete(route('basicdata.branch.destroy', $this->parentBranch->id));
// Assert that the request fails with the expected message
$response->assertJson([
'success' => false,
'message' => 'Cabang dengan anak cabang tidak dapat dihapus.'
]);
$response->assertStatus(422); // Unprocessable Entity
// Verify the parent branch was not deleted
$this->assertDatabaseHas('branches', [
'id' => $this->parentBranch->id,
'deleted_at' => null
]);
}
#[Test]
public function cannot_delete_multiple_branches_if_any_has_children()
{
// Create a child branch
$childBranch = Branch::create([
'code' => 'CHILD',
'name' => 'Child Branch',
'parent_id' => $this->parentBranch->id
]);
// Create another branch without children
$anotherBranch = Branch::create([
'code' => 'ANOTHER',
'name' => 'Another Branch'
]);
// Try to delete both the parent branch and another branch
$response = $this->actingAs($this->user)
->post(route('basicdata.branch.deleteMultiple'), [
'ids' => [$this->parentBranch->id, $anotherBranch->id]
]);
// Assert that the request fails with the expected message
$response->assertJson([
'success' => false,
'message' => 'Beberapa cabang memiliki anak cabang dan tidak dapat dihapus.'
]);
$response->assertStatus(422); // Unprocessable Entity
// Verify neither branch was deleted
$this->assertDatabaseHas('branches', [
'id' => $this->parentBranch->id,
'deleted_at' => null
]);
$this->assertDatabaseHas('branches', [
'id' => $anotherBranch->id,
'deleted_at' => null
]);
}
#[Test]
public function branch_has_correct_children_relationship()
{
// Create a child branch
$childBranch = Branch::create([
'code' => 'CHILD1',
'name' => 'Child Branch 1',
'parent_id' => $this->parentBranch->id
]);
// Create another child branch
$anotherChildBranch = Branch::create([
'code' => 'CHILD2',
'name' => 'Child Branch 2',
'parent_id' => $this->parentBranch->id
]);
// Refresh parent branch
$this->parentBranch->refresh();
// Assert that the parent has two children
$this->assertEquals(2, $this->parentBranch->children()->count());
// Assert that the children collection contains the two child branches
$this->assertTrue($this->parentBranch->children->contains($childBranch));
$this->assertTrue($this->parentBranch->children->contains($anotherChildBranch));
}
}

View File

@@ -0,0 +1,320 @@
<?php
namespace Modules\Basicdata\Tests\Feature;
use Tests\TestCase;
use Modules\Basicdata\Models\Currency;
use Modules\Usermanagement\Models\User;
use Modules\Usermanagement\Models\Role;
use Modules\Usermanagement\Models\Permission;
use Modules\Usermanagement\Models\PermissionGroup;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
class CurrencyControllerTest extends TestCase
{
use RefreshDatabase;
protected $user;
protected $adminRole;
protected $currency;
protected function setUp(): void
{
parent::setUp();
// Create permission group first
$permissionGroup = PermissionGroup::create([
'name' => 'basic-data',
'slug' => 'basic-data'
]);
// Create permissions with permission_group_id
Permission::create([
'name' => 'basic-data.create',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
Permission::create([
'name' => 'basic-data.read',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
Permission::create([
'name' => 'basic-data.update',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
Permission::create([
'name' => 'basic-data.delete',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
Permission::create([
'name' => 'basic-data.export',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
// Create admin role with all permissions
$this->adminRole = Role::create(['name' => 'admin', 'guard_name' => 'web']);
$this->adminRole->givePermissionTo(Permission::all());
// Create a user with admin role
$this->user = User::factory()->create();
$this->user->assignRole($this->adminRole);
// Create a currency for testing
$this->currency = Currency::create([
'code' => 'USD',
'name' => 'US Dollar',
'symbol' => '$',
'decimal_places' => 2,
'created_by' => null,
'updated_by' => null,
'deleted_by' => null,
'authorized_by' => null
]);
}
#[Test]
public function user_with_permission_can_view_currencies_index()
{
$response = $this->actingAs($this->user)
->get(route('basicdata.currency.index'));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_view_currencies_index()
{
// Create a role without permissions
$role = Role::create(['name' => 'viewer', 'guard_name' => 'web']);
// Create a user with the viewer role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('basicdata.currency.index'));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_create_currency()
{
$response = $this->actingAs($this->user)
->get(route('basicdata.currency.create'));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_create_currency()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('basicdata.currency.create'));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_store_currency()
{
$currencyData = [
'code' => 'EUR',
'name' => 'Euro',
'symbol' => '€',
'decimal_places' => 2
];
$response = $this->actingAs($this->user)
->post(route('basicdata.currency.store'), $currencyData);
$response->assertRedirect(route('basicdata.currency.index'));
// Only check the fields we're explicitly setting
$this->assertDatabaseHas('currencies', [
'code' => 'EUR',
'name' => 'Euro',
'symbol' => '€',
'decimal_places' => 2
]);
}
#[Test]
public function user_without_permission_cannot_store_currency()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$currencyData = [
'code' => 'EUR',
'name' => 'Euro',
'symbol' => '€',
'decimal_places' => 2
];
$response = $this->actingAs($user)
->post(route('basicdata.currency.store'), $currencyData);
$response->assertStatus(403);
$this->assertDatabaseMissing('currencies', [
'code' => 'EUR',
'name' => 'Euro'
]);
}
#[Test]
public function user_with_permission_can_edit_currency()
{
$response = $this->actingAs($this->user)
->get(route('basicdata.currency.edit', $this->currency->id));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_edit_currency()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('basicdata.currency.edit', $this->currency->id));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_update_currency()
{
$updatedData = [
'id' => $this->currency->id, // Include the ID in the request
'code' => 'GBP',
'name' => 'British Pound',
'symbol' => '£',
'decimal_places' => 2
];
$response = $this->actingAs($this->user)
->put(route('basicdata.currency.update', $this->currency->id), $updatedData);
$response->assertRedirect(route('basicdata.currency.index'));
// Only check the fields we're explicitly setting
$this->assertDatabaseHas('currencies', [
'id' => $this->currency->id,
'code' => 'GBP',
'name' => 'British Pound',
'symbol' => '£',
'decimal_places' => 2
]);
}
#[Test]
public function user_without_permission_cannot_update_currency()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$updatedData = [
'id' => $this->currency->id, // Include the ID in the request
'code' => 'GBP',
'name' => 'British Pound',
'symbol' => '£',
'decimal_places' => 2
];
$response = $this->actingAs($user)
->put(route('basicdata.currency.update', $this->currency->id), $updatedData);
$response->assertStatus(403);
// Verify the currency wasn't updated - check that it still has the original values
$this->assertDatabaseHas('currencies', [
'id' => $this->currency->id,
'code' => 'USD', // Original value
'name' => 'US Dollar' // Original value
]);
}
#[Test]
public function user_with_permission_can_delete_currency()
{
$response = $this->actingAs($this->user)
->delete(route('basicdata.currency.destroy', $this->currency->id));
$response->assertJson(['success' => true]);
$this->assertSoftDeleted($this->currency);
}
#[Test]
public function user_without_permission_cannot_delete_currency()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->delete(route('basicdata.currency.destroy', $this->currency->id));
$response->assertStatus(403);
$this->assertDatabaseHas('currencies', ['id' => $this->currency->id, 'deleted_at' => null]);
}
#[Test]
public function user_with_permission_can_export_currencies()
{
$response = $this->actingAs($this->user)
->get(route('basicdata.currency.export'));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_export_currencies()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('basicdata.currency.export'));
$response->assertStatus(403);
}
}

View File

@@ -0,0 +1,285 @@
<?php
namespace Modules\Basicdata\Tests\Feature;
use Tests\TestCase;
use Modules\Basicdata\Models\HolidayCalendar;
use Modules\Usermanagement\Models\User;
use Modules\Usermanagement\Models\Role;
use Modules\Usermanagement\Models\Permission;
use Modules\Usermanagement\Models\PermissionGroup;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
class HolidayCalendarControllerTest extends TestCase
{
use RefreshDatabase;
protected $user;
protected $adminRole;
protected $holiday;
protected function setUp(): void
{
parent::setUp();
// Create permission group first
$permissionGroup = PermissionGroup::create([
'name' => 'basic-data',
'slug' => 'basic-data'
]);
// Create permissions with permission_group_id
Permission::create([
'name' => 'basic-data.create',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
Permission::create([
'name' => 'basic-data.read',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
Permission::create([
'name' => 'basic-data.update',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
Permission::create([
'name' => 'basic-data.delete',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
Permission::create([
'name' => 'basic-data.export',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
// Create admin role with all permissions
$this->adminRole = Role::create(['name' => 'admin', 'guard_name' => 'web']);
$this->adminRole->givePermissionTo(Permission::all());
// Create a user with admin role
$this->user = User::factory()->create();
$this->user->assignRole($this->adminRole);
// Create a holiday calendar for testing
$this->holiday = HolidayCalendar::create([
'date' => '2023-01-01',
'description' => 'New Year',
'type' => 'national_holiday'
]);
}
#[Test]
public function user_with_permission_can_view_holidays_index()
{
$response = $this->actingAs($this->user)
->get(route('basicdata.holidaycalendar.index'));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_view_holidays_index()
{
// Create a role without permissions
$role = Role::create(['name' => 'viewer', 'guard_name' => 'web']);
// Create a user with the viewer role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('basicdata.holidaycalendar.index'));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_create_holiday()
{
$response = $this->actingAs($this->user)
->get(route('basicdata.holidaycalendar.create'));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_create_holiday()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('basicdata.holidaycalendar.create'));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_store_holiday()
{
$holidayData = [
'date' => '2023-12-25',
'description' => 'Christmas',
'type' => 'national_holiday'
];
$response = $this->actingAs($this->user)
->post(route('basicdata.holidaycalendar.store'), $holidayData);
$response->assertRedirect(route('basicdata.holidaycalendar.index'));
$this->assertDatabaseHas('holiday_calendars', $holidayData);
}
#[Test]
public function user_without_permission_cannot_store_holiday()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$holidayData = [
'date' => '2023-12-25',
'description' => 'Christmas',
'type' => 'national_holiday'
];
$response = $this->actingAs($user)
->post(route('basicdata.holidaycalendar.store'), $holidayData);
$response->assertStatus(403);
$this->assertDatabaseMissing('holiday_calendars', $holidayData);
}
#[Test]
public function user_with_permission_can_edit_holiday()
{
$response = $this->actingAs($this->user)
->get(route('basicdata.holidaycalendar.edit', $this->holiday->id));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_edit_holiday()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('basicdata.holidaycalendar.edit', $this->holiday->id));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_update_holiday()
{
$updatedData = [
'date' => '2023-01-01',
'description' => 'New Year Updated',
'type' => 'collective_leave'
];
$response = $this->actingAs($this->user)
->put(route('basicdata.holidaycalendar.update', $this->holiday->id), $updatedData);
$response->assertRedirect(route('basicdata.holidaycalendar.index'));
$this->assertDatabaseHas('holiday_calendars', $updatedData);
}
#[Test]
public function user_without_permission_cannot_update_holiday()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$updatedData = [
'date' => '2023-01-01',
'description' => 'New Year Updated',
'type' => 'collective_leave'
];
$response = $this->actingAs($user)
->put(route('basicdata.holidaycalendar.update', $this->holiday->id), $updatedData);
$response->assertStatus(403);
$this->assertDatabaseMissing('holiday_calendars', $updatedData);
}
#[Test]
public function user_with_permission_can_delete_holiday()
{
$response = $this->actingAs($this->user)
->delete(route('basicdata.holidaycalendar.destroy', $this->holiday->id));
$response->assertRedirect(route('basicdata.holidaycalendar.index'));
$this->assertSoftDeleted($this->holiday);
}
#[Test]
public function user_without_permission_cannot_delete_holiday()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->delete(route('basicdata.holidaycalendar.destroy', $this->holiday->id));
$response->assertStatus(403);
$this->assertDatabaseHas('holiday_calendars', ['id' => $this->holiday->id, 'deleted_at' => null]);
}
#[Test]
public function user_with_permission_can_export_holidays()
{
$response = $this->actingAs($this->user)
->get(route('basicdata.holidaycalendar.export'));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_export_holidays()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('basic-data.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('basicdata.holidaycalendar.export'));
$response->assertStatus(403);
}
}