Automating PPh 21 and BPJS payroll is key to operational efficiency in healthcare facilities. This article discusses concrete steps, technology, and best practices for implementing an integrated payroll system, reducing errors, and ensuring regulatory compliance.
Payroll management in healthcare facilities (Faskes), both hospitals and clinics, is often one of the most complex and time-consuming administrative burdens. The manual process of calculating PPh 21 and BPJS contributions (Health and Employment) involves many dynamic variables: basic salary, allowances, overtime, deductions, employment status, and employee dependent status. Not to mention, tax and social security regulations that continue to evolve, such as those set forth in the Tax Regulations Harmonization Law (UU HPP) Number 7 of 2021 and various technical regulations from the Directorate General of Taxes (DJP) and the Social Security Administering Body (BPJS). Small errors can result in fines, troublesome audits, and decreased employee morale. Amid the demand to provide the best healthcare services, Faskes should not be burdened by complicated administration.
This guide is designed to provide concrete solutions for Hospital IT Managers, clinic owners, operations managers, and decision-makers seeking efficiency through technology. We will thoroughly examine everything from the basic concepts of automation, technical implementation details with runnable code examples, strategies for handling data integration and errors, to the best practices you should apply. Our goal is to equip you with actionable knowledge to build or adopt an automated payroll system that is not only accurate and regulation-compliant, but also supports your Faskes's main focus: optimal patient care.
PPh 21 and BPJS payroll automation is the process of using software to automatically calculate and manage all aspects of employee payroll, from net salary calculation, tax deductions, BPJS contributions, to report generation. Its main benefits are very significant: first, drastically reducing human error that often occurs in manual calculations. Second, saving time and HR and finance resources that can be allocated to other strategic tasks. Third, ensuring accuracy and consistency of calculations in accordance with the latest regulations, so that Faskes avoids potential sanctions and fines. Fourth, increasing transparency and ease of data access for employees.
The main components in this automation system include: employee master data (basic salary, allowances, PTKP status, BPJS number), PPh 21 calculation module, BPJS calculation module, and reporting module. The main regulations used as references are: Law No. 7 of 2021 on the Harmonization of Tax Regulations (UU HPP) which introduced changes to PPh 21 rates, Director General of Taxes Regulation No. PER-16/PJ/2016 on Technical Guidelines for the Procedures for Withholding, Depositing and Reporting Article 21 Income Tax, Presidential Regulation No. 82 of 2018 on Health Insurance, and Minister of Manpower Regulation No. 5 of 2021 on Procedures for Implementing the Work Accident Insurance, Death Insurance, and Old Age Insurance Programs. Understanding these regulations is a crucial foundation so that the system built remains relevant and legal.
As a concrete example, let us simulate the calculation for an employee with a monthly gross salary of Rp 10,000,000 and PTKP status K/0 (Married without dependents). PPh 21 will be calculated based on net income after deducting occupational expenses (5% of gross income, maximum Rp 6,000,000 per year or Rp 500,000 per month) and pension/JHT contributions paid by the employee. Then, net income is reduced by annual PTKP (Rp 58,500,000 for K/0) to obtain Taxable Income (PKP). This PKP will be subject to the progressive PPh 21 rates (for example 5% for PKP up to Rp 60 million, 15% for PKP Rp 60 million - Rp 250 million, etc. according to the HPP Law). For BPJS, the BPJS Health contribution is charged 1% to the employee and 4% to the company from wages, with a wage cap of Rp 12,000,000. BPJS Employment has JHT components (2% employee, 3.7% company), JKK (0.24-1.74% company), JKM (0.3% company), and JP (1% employee, 2% company), also with a wage cap of Rp 12,000,000. The automation system will perform these complex calculations in seconds for all employees.
Building a robust automated payroll system requires a well-planned architecture. As a data foundation, we can utilize PostgreSQL 16 as a reliable and open-source relational database management system, known for its high data integrity and scalability. For the backend side, frameworks such as Laravel 11.x (PHP) or Node.js 20 LTS with the Express.js framework are very suitable for building RESTful APIs that serve calculation and data management requests. Meanwhile, the user interface can be developed using React 18 or Vue 3 for a responsive and interactive user experience.
Integration with other systems, such as Hospital Management Information Systems (SIMRS) or Clinic Management Information Systems (SIM Klinik), is key to data efficiency. Interoperability standards such as FHIR R4 (Fast Healthcare Interoperability Resources Release 4) or HL7 v2.5.1 can be used to exchange employee data, attendance, or other relevant information securely and in a structured manner. For example, new employee data or status changes can be published from SIMRS to the payroll system via FHIR resources such as Patient or PractitionerRole that are extended for payroll information.
The Employee Data Module must be able to store personal information, basic salary, fixed and non-fixed allowances, PTKP status, BPJS Kesehatan and Ketenagakerjaan numbers, entry date, and employment status (permanent, contract). The PPh 21 Calculation Module will implement the algorithm according to PER-16/PJ/2016, covering the calculation of gross income, occupational expenses (5% of gross income, maximum IDR 6,000,000/year), pension contributions, and determination of the latest PTKP according to employee status. It is important to ensure that the PTKP constants and progressive PPh 21 rates are always up-to-date.
The BPJS Calculation Module will calculate BPJS Kesehatan contributions (1% employee, 4% company) and BPJS Ketenagakerjaan (JHT, JKK, JKM, JP) based on the contribution percentages and wage limits applicable according to Perpres No. 82 of 2018 and Permenaker No. 5 of 2021. The current upper wage limit for calculating BPJS Kesehatan and Ketenagakerjaan contributions is IDR 12,000,000. Finally, the Reporting Module functions to generate CSV or TXT files that comply with the e-SPT PPh 21 format and BPJS Ketenagakerjaan SIPP data, facilitating monthly and annual reporting processes without the need for repeated manual entry. For security, sensitive data must be encrypted using standards such as AES-256, and access to the system is restricted with JWT (JSON Web Token) authentication and role-based access control (RBAC).
Implementing the PPh 21 and BPJS calculation logic is the core of the automation system. Below is an example code in PHP (for Laravel 11.x) and Python that demonstrates how this logic can be realized.
This code illustrates a Pph21Calculator service in Laravel that calculates monthly and annual PPh 21 based on gross salary, PTKP status, and Pension/JHT contributions paid by the employee. This calculation follows the latest PPh 21 rate layers according to the HPP Law and applicable PTKP.
<?phpnamespace App
Services;
use Illuminate
Support
Facades
Log;
class Pph21Calculator
{
const TARIF_PPH_LAPISAN = [
['batas' => 60000000, 'tarif' => 0.05],
['batas' => 250000000, 'tarif' => 0.15],
['batas' => 500000000, 'tarif' => 0.25],
['batas' => 5000000000, 'tarif' => 0.30],
['batas' => INF, 'tarif' => 0.35]
];
const PTKP_TAHUNAN = [
'TK0' => 54000000,
'K0' => 58500000,
'K1' => 63000000,
'K2' => 67500000,
'K3' => 72000000,
];
public function calculatePph21($gajiBrutoBulanan, $statusPtkpKode = 'TK0', $iuranJpBulanan = 0, $iuranJhtBulanan = 0)
{
Log::info("Starting PPh 21 calculation for bruto: {$gajiBrutoBulanan}, PTKP: {$statusPtkpKode}");
$penghasilanBrutoTahunan = $gajiBrutoBulanan * 12;
$biayaJabatanTahunan = min($penghasilanBrutoTahunan * 0.05, 6000000);
Log::debug("Biaya Jabatan Tahunan: {$biayaJabatanTahunan}");
$pengurangLainTahunan = ($iuranJpBulanan + $iuranJhtBulanan) * 12;
Log::debug("Pengurang Lain Tahunan (JP+JHT): {$pengurangLainTahunan}");
$penghasilanNetoTahunan = $penghasilanBrutoTahunan - $biayaJabatanTahunan - $pengurangLainTahunan;
Log::debug("Penghasilan Neto Tahunan: {$penghasilanNetoTahunan}");
if ($penghasilanNetoTahunan <= 0) {
return ['pph21_bulanan' => 0, 'pph21_tahunan' => 0, 'details' => ['penghasilan_neto_tahunan' => $penghasilanNetoTahunan]];
}
$ptkpTahunan = self::PTKP_TAHUNAN[strtoupper($statusPtkpKode)] ?? self::PTKP_TAHUNAN['TK0'];
Log::debug("PTKP Tahunan: {$ptkpTahunan}");
$pkpTahunan = max(0, $penghasilanNetoTahunan - $ptkpTahunan);
Log::debug("PKP Tahunan: {$pkpTahunan}");
$pph21Tahunan = 0;
$sisaPkp = $pkpTahunan;
foreach (self::TARIF_PPH_LAPISAN as $lapisan) {
if ($sisaPkp <= 0) break;
$batasLapisan = $lapisan['batas'];
$tarif = $lapisan['tarif'];
$pkpUntukLapisan = min($sisaPkp, $batasLapisan);
if ($lapisan['batas'] == INF) {
$pkpUntukLapisan = $sisaPkp;
}
$pph21Tahunan += $pkpUntukLapisan * $tarif;
$sisaPkp -= $pkpUntukLapisan;
Log::debug("Lapisan: {$batasLapisan}, Tarif: {$tarif}, PKP untuk lapisan: {$pkpUntukLapisan}, PPh 21 sementara: {$pph21Tahunan}");
}
$pph21Bulanan = round($pph21Tahunan / 12);
Log::info("PPh 21 Tahunan: {$pph21Tahunan}, PPh 21 Bulanan: {$pph21Bulanan}");
return [
'pph21_bulanan' => $pph21Bulanan,
'pph21_tahunan' => $pph21Tahunan,
'details' => [
'penghasilan_bruto_tahunan' => $penghasilanBrutoTahunan,
'biaya_jabatan_tahunan' => $biayaJabatanTahunan,
'pengurang_lain_tahunan' => $pengurangLainTahunan,
'penghasilan_neto_tahunan' => $penghasilanNetoTahunan,
'ptkp_tahunan' => $ptkpTahunan,
'pkp_tahunan' => $pkpTahunan,
]
];
}
}The code above defines constants for PPh 21 rates and PTKP according to regulations. The calculatePph21 function takes monthly gross salary, PTKP status, and BPJS contributions as input. It calculates annual gross income, subtracts occupational expenses and other deductions to obtain annual net income. After subtracting PTKP, the Taxable Income (PKP) is obtained, which is then subjected to progressive rates. The output is monthly and annual PPh 21 along with calculation details. To use this in Laravel, you can instantiate Pph21Calculator in a controller or another service.
This Python code shows how BPJS Kesehatan and Ketenagakerjaan contributions can be calculated based on the employee's basic salary and fixed allowances. It includes contribution percentages and the relevant wage ceiling.
# File: bpjs_calculator.pyimport mathclass BPJSCalculator:
MAX_UPAH_BPJS_KETENAGAKERJAAN = 12000000
MAX_UPAH_BPJS_KESEHATAN = 12000000
PERSEN_JK_PERUSAHAAN = 0.04
PERSEN_JK_KARYAWAN = 0.01
PERSEN_JKK_PERUSAHAAN = 0.0054
PERSEN_JKM_PERUSAHAAN = 0.003
PERSEN_JHT_PERUSAHAAN = 0.037
PERSEN_JHT_KARYAWAN = 0.02
PERSEN_JP_PERUSAHAAN = 0.02
PERSEN_JP_KARYAWAN = 0.01
def calculate_bpjs_contributions(self, gaji_pokok_dan_tunjangan_tetap):
upah_hitung_jkk_jkm_jht_jp = min(gaji_pokok_dan_tunjangan_tetap, self.MAX_UPAH_BPJS_KETENAGAKERJAAN)
upah_hitung_jk = min(gaji_pokok_dan_tunjangan_tetap, self.MAX_UPAH_BPJS_KESEHATAN)
iuran_jk_perusahaan = round(upah_hitung_jk * self.PERSEN_JK_PERUSAHAAN)
iuran_jk_karyawan = round(upah_hitung_jk * self.PERSEN_JK_KARYAWAN)
iuran_jkk_perusahaan = round(upah_hitung_jkk_jkm_jht_jp * self.PERSEN_JKK_PERUSAHAAN)
iuran_jkm_perusahaan = round(upah_hitung_jkk_jkm_jht_jp * self.PERSEN_JKM_PERUSAHAAN)
iuran_jht_perusahaan = round(upah_hitung_jkk_jkm_jht_jp * self.PERSEN_JHT_PERUSAHAAN)
iuran_jht_karyawan = round(upah_hitung_jkk_jkm_jht_jp * self.PERSEN_JHT_KARYAWAN)
iuran_jp_perusahaan = round(upah_hitung_jkk_jkm_jht_jp * self.PERSEN_JP_PERUSAHAAN)
iuran_jp_karyawan = round(upah_hitung_jkk_jkm_jht_jp * self.PERSEN_JP_KARYAWAN)
total_iuran_karyawan = iuran_jk_karyawan + iuran_jht_karyawan + iuran_jp_karyawan
total_iuran_perusahaan = iuran_jk_perusahaan + iuran_jkk_perusahaan + iuran_jkm_perusahaan + iuran_jht_perusahaan + iuran_jp_perusahaan
return {
"iuran_bpjs_kesehatan_perusahaan": iuran_jk_perusahaan,
"iuran_bpjs_kesehatan_karyawan": iuran_jk_karyawan,
"iuran_jkk_perusahaan": iuran_jkk_perusahaan,
"iuran_jkm_perusahaan": iuran_jkm_perusahaan,
"iuran_jht_perusahaan": iuran_jht_perusahaan,
"iuran_jht_karyawan": iuran_jht_karyawan,
"iuran_jp_perusahaan": iuran_jp_perusahaan,
"iuran_jp_karyawan": iuran_jp_karyawan,
"total_iuran_karyawan": total_iuran_karyawan,
"total_iuran_perusahaan": total_iuran_perusahaan,
"upah_dasar_bpjs_ketenagakerjaan": upah_hitung_jkk_jkm_jht_jp,
"upah_dasar_bpjs_kesehatan": upah_hitung_jk
}
# Contoh penggunaan:
# calculator = BPJSCalculator()
# gaji_bruto = 15000000
# hasil_bpjs = calculator.calculate_bpjs_contributions(gaji_bruto)
# print(hasil_bpjs)This BPJSCalculator class defines constants for the upper wage limit and contribution percentages for BPJS Kesehatan and Ketenagakerjaan. The calculate_bpjs_contributions function accepts basic salary and fixed allowances, then calculates the contributions for each BPJS component for both employee and company. It is important to remember that the JKK percentage can vary based on the business sector (risk class), where in this example an average value is used. These figures should always be verified with the latest regulations from the relevant BPJS. Both of these code examples demonstrate the importance of modeling business rules and regulations into clean and structured code.
Data integration between SIMRS/SIM Klinik and the automatic payroll system is essential to eliminate data duplication and ensure consistency. A RESTful API is the most common and efficient method for data exchange. Below is an example of a realistic JSON payload that might be sent from SIMRS to the payroll system to process monthly salaries.
{
"periode_payroll": "2024-06",
"data_karyawan": [
{
"id_karyawan": "EMP001",
"nama": "Budi Santoso",
"nik": "3301xxxxxxxxxxxx",
"npwp": "01.234.567.8-901.000",
"status_ptkp": "K/0",
"gaji_pokok": 8500000,
"tunjangan_tetap": 1500000,
"tunjangan_tidak_tetap": 500000,
"potongan_lain": 100000,
"bpjs_kesehatan_no": "0001234567890",
"bpjs_ketenagakerjaan_no": "123456789012345",
"tanggal_masuk": "2020-01-15",
"status_kepegawaian": "Tetap"
},
{
"id_karyawan": "EMP002",
"nama": "Siti Aminah",
"nik": "3302xxxxxxxxxxxx",
"npwp": "02.345.678.9-012.000",
"status_ptkp": "K/1",
"gaji_pokok": 10000000,
"tunjangan_tetap": 2000000,
"tunjangan_tidak_tetap": 750000,
"potongan_lain": 0,
"bpjs_kesehatan_no": "0009876543210",
"bpjs_ketenagakerjaan_no": "987654321098765",
"tanggal_masuk": "2019-03-01",
"status_kepegawaian": "Tetap"
}
]
}The payload above is a representation of employee data needed for the payroll calculation process. Each employee object includes a unique identity, tax information (NPWP, PTKP status), salary details, and BPJS membership numbers. This data will be sent to the payroll system's API endpoint (for example POST /api/payroll/process) which will then validate and process it.
During the integration process, errors can occur for various reasons, ranging from invalid data to connectivity issues. It is important to have a clear error handling mechanism. Here is an example of an error message from the payroll system:
{
"status": "error",
"code": 400,
"message": "Validation Failed",
"errors": {
"data_karyawan.0.npwp": ["NPWP is invalid or missing for employee EMP001."],
"data_karyawan.1.status_ptkp": ["Invalid PTKP status 'K/1' for employee EMP002. Expected 'K/0', 'K/1', 'K/2', 'K/3', 'TK/0'."]
}
}This error message indicates that there is a data validation problem for employees with ID EMP001 (invalid NPWP) and EMP002 (PTKP status does not match the expected format). To handle this kind of error, there are several strategies:
Yes, a well-designed automated payroll system must be able to accommodate various types of employment status. The calculation of PPh 21 for contract (non-permanent) employees uses a different method than for permanent employees, often using daily or monthly rates without full PTKP, or with proportional PTKP if certain conditions are met. This employment status information must be one of the main attributes input and managed in the employee master data, allowing the system to automatically apply the appropriate calculation logic.
A total overhaul is not necessary if the system is designed with good modularity. The only parts that need updating are the rate calculation module, PTKP constants, or wage threshold limits. With an architecture that separates business logic from other components, changes to constants or calculation formulas can be implemented and tested independently without disrupting other parts of the system, minimizing downtime and development costs.
Data security is a top priority in a payroll system. Sensitive data such as salaries, NIK, NPWP, and BPJS numbers must be encrypted when stored in the database (at rest) using strong algorithms such as AES-256, and when transferred (in transit) using HTTPS/TLS. In addition, access to the system must be protected with multi-factor authentication and strict role-based access control (RBAC), ensuring that only authorized personnel can view or modify certain data. A comprehensive audit trail is also important to track every activity.
Very possible and highly recommended. Generally, an automated payroll system will generate a bank transfer file in CSV or TXT format compatible with banking systems (for example, BCA KlikPay, Mandiri Cash Management, or other bank platforms). Some major banks even provide APIs for more advanced integration, allowing salary payment instructions to be sent directly from the payroll system to the bank without the need for manual file uploads, making the payment process faster and minimizing errors.
Calculation accuracy is guaranteed through several layers. First, the use of formulas that comply with the latest regulations and are updated regularly. Second, strict validation of input data to prevent errors from the start. Third, extensive unit and integration testing with various data scenarios to verify the correctness of the logic. Fourth, periodic reconciliation mechanisms with official tax or BPJS reports. Fifth, comparing the results of automated calculations with manual calculations on data samples to ensure consistency and accuracy.
The implementation time for an automated payroll system varies greatly depending on the complexity of the healthcare facility, the number of employees, and the level of customization required. For a medium-scale healthcare facility with around 100-300 employees, implementation can take 3-6 months. This includes the requirements analysis phase, system design, development, intensive testing, data migration, and user training. Larger healthcare facilities with complex integration needs or deep customization may require more time, potentially reaching 6-12 months or more.
Automating PPh 21 and BPJS payroll is no longer just an option, but an urgent need for healthcare facilities that want to achieve maximum operational efficiency. By implementing the right system, you not only reduce administrative burden and minimize the risk of errors, but also ensure compliance with constantly changing regulations, while enabling your team to focus on the core of healthcare services. The complexity of regulations, the need for seamless data integration, and the technical expertise required are indeed challenges. However, the long-term benefits obtained will far exceed the initial investment. As an Operations Manager and Full Stack Developer with extensive experience in SIMRS, Clinic Management Systems, BPJS/SatuSehat/FHIR integration, and ERP solutions, Nugroho Setiawan is ready to be your partner. Don't let manual processes hinder the growth of your healthcare facility. Contact us for further consultation and let's realize an efficient, accurate, and automated payroll solution that suits the specific needs of your healthcare facility.
Related solutions by PT Pilar Inovasi Informatika (Pilar Inovasi IT products): SIMRS (Hospital Information System) BPJS & SatuSehat Integration. Need a free consultation?
Belum ada komentar. Jadilah yang pertama!