CEK KERJAAN MASING MASING

Created: 10/5/2026 Updated: 10/5/2026

Backend Import and Payroll Findings

Summary

This review identified class-loading failures, missing authorization checks, payroll calculation inconsistencies, unsafe null handling, import data integrity problems, and export compatibility issues.

High

1. SekolahImport cannot load because methods are declared twice (DONE)

File: app/Jobs/Imports/SekolahImport.php

SekolahImport declares data_tagihan() twice and data_pembayaran_term() twice:

  • data_tagihan(): line 612 and line 970
  • data_pembayaran_term(): line 738 and line 1096

PHP fails with:

Cannot redeclare App\Jobs\Imports\SekolahImport::data_tagihan()

This prevents the class from loading. Imports dispatched through SekolahImport, including data_mata_pelajaran, data_guru, data_nilai, data_tagihan, data_pembayaran_term, and data_pembayaran_siswa, will fail.

Recommendation: Remove or merge the duplicate method declarations and keep one authoritative implementation for each import method.

2. Jabatan import uses DB without importing the facade (DONE)

File: app/Jobs/Imports/MasterDataImports.php:235

The code calls:

DB::beginTransaction();

but the file does not import:

use Illuminate\Support\Facades\DB;

Because the class namespace is App\Jobs\Imports, PHP attempts to resolve App\Jobs\Imports\DB, causing a class-not-found error whenever data_jabatan is imported.

Recommendation: Add the missing facade import.

3. Payroll recount endpoint lacks company ownership validation (DONE)

File: app/Http/Controllers/Company/PayrollController.php:1098

The endpoint loads payroll only by ID:

$payroll = Payroll::where('payroll_id', $payrollId)->first();

The query is not constrained by the authenticated company:

->where('perusahaan_id', $data_perusahaan->perusahaan_id)

If an authenticated company user submits another company’s payroll_id, the endpoint may recalculate and modify another tenant’s payroll. The same missing ownership check exists before loading the employee and division.

Recommendation: Scope every payroll, employee, division, and related record lookup to the authenticated company before performing any recalculation or update.

4. Payroll totals may include job allowances inconsistently

Files: app/Http/Controllers/Company/PayrollController.php:1038-1054, :1229-1233, and recalculatePayrollTotals()

The new helper adds tunjangan_total to payroll_total, but the stored payroll_kompensasi value excludes it:

$payroll->payroll_kompensasi = $calc['kompensasi_total'];
$payroll->payroll_total = $calc['total'];

This creates a mismatch:

payroll_total != base salary + payroll_kompensasi - payroll_potongan

Additionally, ubah_status() calculates allowance data before calling the helper, but only returns it in the response. The allowance is not persisted as a separate payroll component. Later recalculations can therefore change historical payroll totals when the employee’s current job allowances change.

Recommendation: Define one canonical payroll calculation model. Persist job allowances as payroll components or snapshot values, and ensure all stored totals reconcile with their component fields.

5. Payroll recalculation can fail when the employee has no job relation

File: app/Http/Controllers/Company/PayrollController.php

The new logic contains:

foreach ($karyawan->jabatan->tunjangan ?? [] as $t)

If Karyawan::find() returns null, or if jabatan is null, dereferencing $karyawan->jabatan can fail before the null-coalescing fallback is applied. The same pattern appears in recalculatePayrollTotals() and both status methods.

Recommendation: Validate the employee before use and access the relation through a guarded path, for example $karyawan?->jabatan?->tunjangan ?? [], with an explicit failure response when the employee is required.

6. Manual PPH/BPJS override fields may not exist in the database

File: app/Http/Controllers/Company/PayrollController.php:1028-1035

The code indicates that the fields require a migration:

// tambahkan via migration jika belum

No migration or model/schema definition was found for:

  • pph_amount_override
  • bpjs_jht_amount_override

If these columns are absent in the deployed database, save() will fail with an undefined-column database error.

Recommendation: Verify the deployed schema and add a migration before releasing code that reads or writes these fields. Confirm the migration has been applied in every environment.

7. Lembur export can fail when user_level is an integer (IGNORE)

File: app/Jobs/Exports/ProcessExportPengajuan.php:107 and :195

The code assumes:

$userLevelId['level_id']

However, export payloads accept user_level in multiple shapes, and the previous implementation passed it directly as an ID. If the payload contains an integer, PHP raises:

Cannot access offset of type int on int

The change also ignores user_level inside $filters and reads only from the top-level $payload.

Recommendation: Normalize the payload shape before use, supporting the documented integer and object/array forms, and define precedence between $filters and $payload.

Medium

8. Task import can leave an orphan task if pivot insertion fails

File: app/Jobs/Imports/TugasImport.php:121-147

The task is inserted first, followed by employee pivot rows. There is no transaction. If a pivot insert fails, the catch block reports failure but the task remains in the database without its intended assignments.

Recommendation: Wrap task creation and pivot insertion in one database transaction and roll back both when any assignment fails.

9. Task import silently accepts unknown employees

File: app/Jobs/Imports/TugasImport.php:133-149

Employee names are loaded with:

->whereIn('karyawan_nama', $karyawanNames)

Missing names are ignored. The import can report success even though some requested employees were not attached, leaving an incomplete assignment set.

Recommendation: Compare requested names with matched names and reject or explicitly report every unknown employee before committing the task.

10. Task import may reject valid status values from existing templates

File: app/Jobs/Imports/TugasImport.php:54-55

The accepted statuses were reduced to:

['draft', 'inprogress', 'finished']

Existing templates or clients using published, done, approved, or rejected are silently converted to draft and recorded as skipped. This can alter task workflow state instead of clearly rejecting the row.

Recommendation: Preserve the established status vocabulary or provide an explicit, documented mapping. Do not silently change workflow state.

11. Payment imports use ILIKE without wildcards

File: app/Jobs/Imports/SekolahImport.php:870 and :883

These queries use:

->where('karyawan_nama', 'ilike', $namaSiswa)
->where('pb_term_name', 'ilike', $jenisPembayaran)

ILIKE without % behaves as case-insensitive equality, not a partial match. This is inconsistent with the tagihan lookup and can fail when the spreadsheet contains extra spacing or formatting differences.

Recommendation: Normalize spreadsheet values and define whether matching should be exact or partial. If partial matching is intended, add controlled wildcards and avoid ambiguous matches.

12. Payment import allows ambiguous partial tagihan matches

File: app/Jobs/Imports/SekolahImport.php:898-900

The query uses a partial match followed by first():

->where('tagihan_nama', 'ilike', '%' . $namaTagihan . '%')
->first()

If multiple bills contain the same text, the first arbitrary match is selected. A payment may therefore be linked to the wrong bill.

Recommendation: Match using a unique identifier where possible. Otherwise, require exactly one match and report ambiguous rows as failures.

13. Duplicate rows within one spreadsheet are not reliably prevented

The importers check the database before inserting, but do not maintain an in-memory set or consistently use a unique constraint. Duplicate rows in one import can both pass the existence check before either is inserted, depending on execution and database constraints.

This affects at least:

  • tagihan imports
  • payment imports
  • kasbon imports
  • claim/beban imports
  • leave-cashout imports

Recommendation: Define a row identity for each importer, reject duplicate keys within the input batch, and enforce matching database uniqueness where appropriate.

14. Leave-cashout import does not verify leave balance or cuti_id

File: app/Jobs/Imports/PengajuanImport.php, data_pencairan_cuti

The importer creates records with:

'cuti_id' => null

It validates only employee, date, quantity, description, and status. If existing application logic expects a linked leave record or checks available leave balance, imported records can be invalid or impossible to process later.

Recommendation: Resolve and validate the related leave record, verify the available balance, and reject rows that cannot be linked safely.

15. Beban import rejects formatted currency values despite adding a parser

File: app/Jobs/Imports/PengajuanImport.php

parseNumber() was added, but data_beban still checks:

if (empty($nominalRaw) || !is_numeric($nominalRaw))

Values such as Rp 1.000.000 or 1,000.00 are rejected even though the new parser appears intended to support formatted values.

Recommendation: Parse and validate the normalized numeric value, rather than validating the raw formatted string with is_numeric().

16. Jabatan salary parsing misinterprets decimal values

File: app/Jobs/Imports/MasterDataImports.php:229-231

The parser uses:

str_replace(['.', ','], ['', '.'], ...)

This is not locale-safe. Examples include:

  • 1.500.50 becomes 1500.50, possibly accidentally acceptable
  • 1500.50 becomes 150050
  • 1,500.50 becomes 1.50050

The logic can inflate or corrupt salary values.

Recommendation: Define the supported input locale and parse thousands and decimal separators deterministically. Reject ambiguous formats instead of guessing.

17. Allowance matching is case-insensitive in PHP but case-sensitive in SQL

File: app/Jobs/Imports/MasterDataImports.php:282-285

The code performs exact SQL matching:

->whereIn('tunjangan_nama', $names)

and lowercases results in PHP afterward. Spreadsheet values with different casing are not returned by the database query and are incorrectly reported as unmatched.

Recommendation: Normalize both sides before querying, use a case-insensitive database comparison, or resolve names through a canonical lookup.

18. Allowance import can attach duplicate pivot IDs

File: app/Jobs/Imports/MasterDataImports.php:287-300

Duplicate names in the comma-separated allowance list can add the same tunjangan_id more than once. If the pivot table has a unique constraint, the transaction fails. If it does not, duplicate allowance rows may be created.

Recommendation: Normalize and deduplicate allowance names and pivot IDs before inserting. Enforce uniqueness on the pivot table where appropriate.

Low / Correctness Risks

19. Payroll search endpoint has no explicit numeric validation for month/year

File: app/Http/Controllers/Company/PayrollController.php:120-125

month and year are only marked as required. Values such as abc, 0, or invalid month values can reach the database query.

Recommendation: Validate month as an integer from 1 through 12 and year as a valid integer within the supported payroll range.

20. Export selection handling is inconsistent

Files: app/Jobs/Exports/ProcessExportNilai.php:18,85-89 and app/Jobs/Exports/ProcessExportMapel.php:37-41

ProcessExportNilai keeps unknown selected columns and maps them to null. ProcessExportMapel filters invalid columns. This produces inconsistent behavior across export screens and can create blank columns in nilai exports.

Recommendation: Use one shared column-selection validator and reject or filter unknown fields consistently.

21. Jabatan export assumes every mapped item is a model

File: app/Export/TemplateJabatanExport.php:45

The default empty array is safe because map() is not called. However, callers passing arrays or incomplete objects will fail when the code accesses $data->tunjangan.

Recommendation: Validate the export input shape and guard optional model relations before mapping.

22. No transaction around paired entity/history inserts

Several school imports create a primary record and then its history record separately. If the history insert fails, the primary record remains while the import reports failure.

This applies to mapel, nilai, guru, and related imports.