A practical guide to building automated financial reports that comply with PSAK standards: designing the chart of accounts, automated journal entries, scheduled period closing, and generating income statements, balance sheets, and cash flow statements with Python code examples.
Many hospitals, clinics, and distribution companies in Yogyakarta and Magelang still close the month the old way: export CSV from SIMRS or ERP, paste into Excel, then manually recap for 3–5 working days. As a result, management reports are only available mid-next month, BPJS receivable figures often do not match the general ledger, and internal audits always find missing adjusting journals. Yet transactions have been 100% digital since billing, cashier, and purchasing run in the system. The problem is not the data, but the absence of an accounting layer: a consistent chart of accounts, automatic journal rules, and a scheduled month-end close process. This article discusses how to build that layer so that the income statement, balance sheet, and cash flow statement are presented automatically following the PSAK framework, complete with code examples you can run immediately.
Foundation: Chart of Accounts and Automatic Journal Rules
The first step is not coding, but tidying up the chart of accounts (COA). Use a leveled 5-digit structure: 1-xxxx assets, 2-xxxx liabilities, 3-xxxx equity, 4-xxxx revenue, 5-xxxx expenses, and 6-xxxx statistical accounts for visits or occupancy. For a clinic with eight polyclinics, separate physician service revenue (4-1100), facility services (4-1200), laboratory (4-1300), and pharmacy (4-1400). This separation is not merely for neatness: physician profit sharing is calculated from different accounts, and PSAK 72 requires disclosure of revenue by category.
After the COA, create a mapping matrix from transaction events to standard journal templates. A real example: an inpatient with a bill of Rp4.750.000 consisting of medical services Rp2.100.000, accommodation Rp1.850.000, laboratory Rp560.000, and pharmacy Rp240.000. The system must automatically create a debit to Patient Receivables 1-2100 of Rp4.750.000 and credits to four revenue accounts according to their portions. If the patient pays in cash, the paired journal debits Cash 1-1100. For BPJS claims, receivables are separated into 1-2200 with a sub-ledger per claim number and verification date, because PSAK 71 requires an allowance for impairment losses calculated by receivable aging bucket (0–30, 31–60, 61–90, >90 days).
The next important rule is immutability. Every journal line must store source_type, source_id, created_by, and approved_by. With a unique constraint on the combination (source_type, source_id, line_no), the posting process becomes idempotent: a job that is rerun will not duplicate the journal. Corrections are made through reversing journals in the current period, not by changing old lines. This is what makes your reports pass audit.
If you manage multiple entities (parent clinic, pharmacy, and support units), prepare a uniform COA per entity plus intercompany accounts 1-1900. Elimination of intercompany transactions is performed automatically in the consolidation layer so that combined reports do not inflate, in line with the principles of PSAK 65 on consolidated financial statements.
Month-End Close Automation: from Journal to Trial Balance
The minimal architecture is three tables: account, journal_entry (header containing date, period, status draft/posted), and journal_line (account_id, debit, credit). Add a period table with a locked flag. A stack proven sufficient for a volume of 50.000 journal lines per month is Python 3.12, PostgreSQL 16, SQLAlchemy 2.0, and pandas 2.2. The query below produces a trial balance while verifying balance and net income for one period.
import pandas as pd
q = 'SELECT a.code, a.type, l.debit, l.credit FROM journal_line l JOIN account a ON a.id = l.account_id WHERE l.period = :p'
df = pd.read_sql(q, engine, params={'p': '2025-03-01'}) # engine: SQLAlchemy 2.0 + PostgreSQL 16
df['net'] = df.debit - df.credit # debit positif, kredit negatif
assert abs(df['net'].sum()) < 0.01, 'Jurnal tidak balance'
tb = df.groupby(['code', 'type'], as_index=False)['net'].sum()
pendapatan = -tb.loc[tb.type == 'REVENUE', 'net'].sum()
beban = tb.loc[tb.type == 'EXPENSE', 'net'].sum()
print(f'Laba bersih Maret 2025: Rp {pendapatan - beban:,.0f}')In addition to balance, automatic month-end close must run three accrual jobs. First, straight-line depreciation of fixed assets: an ultrasound machine of Rp480.000.000, useful life 8 years, residual value Rp40.000.000, so the monthly expense is Rp4.583.333. Second, building lease amortization according to PSAK 73 for contracts longer than 12 months. Third, accrual of physician services and loan interest whose invoices only arrive the following month. All jobs store logs of duration, number of lines, and total value so they can be verified.
Scheduling can simply use an existing scheduler, for example Laravel 11: $schedule->job(new ClosePeriodJob('2025-03'))->monthlyOn(1, '01:00'); followed by notification to the admin if there are draft journals not yet posted. Bank reconciliation is run weekly with automatic matching based on reference number; unidentified differences go into suspense account 1-9999 and must be zero before the period is locked.
Preparing an Audit-Ready Income Statement, Balance Sheet, and Cash Flow Statement
PSAK 1 requires a complete set: statement of financial position, statement of profit or loss and other comprehensive income, statement of changes in equity, statement of cash flows, and notes to the financial statements, all with comparative information for the previous period. Best practice is to create a report_line table that maps account codes to report lines. The finance team can change the presentation structure without touching application code, and last year's reports remain consistent when compared.
For cash flow, use the indirect method in accordance with PSAK 2. Example: March net profit of Rp312.000.000, depreciation of Rp96.000.000, an increase in BPJS receivables of Rp145.000.000, and a decrease in supplier payables of Rp38.000.000 result in operating cash flow of Rp225.000.000. If beginning cash is Rp480.000.000, ending cash should be Rp705.000.000 — this figure must exactly match the Cash and Bank balance in the balance sheet. Make the difference between the two an automated assertion, not a matter for manual discussion.
From a taxation perspective, PSAK 46 requires recognition of deferred tax on temporary differences, especially fiscal versus commercial depreciation. Keep a table of permanent and temporary differences per account, then generate an automated fiscal reconciliation as an attachment. Accruals for PPh 21 and PPh 23 are recorded in the month the transaction occurs, not at payment via e-Bupot Unifikasi or Coretax, so period expenses do not shift.
Finally, distribution. Store a snapshot of the locked period so figures do not change when records are revised, render PDFs with dompdf 2.x or Excel with openpyxl 3.1, and expose a materialized view such as v_trial_balance to Metabase 0.50 for management dashboards. That way, the hospital director can see margin per unit every day, not on the 20th of each month.
Best Practices
- Lock the period after closing the books and prohibit edits; all corrections go through adjusting journals in the current period.
- Store a complete audit trail (source_type, source_id, created_by, approved_by) on every journal line.
- Run daily trial balance tests, not monthly, with automated alerts if the difference exceeds Rp1.
- Perform weekly bank reconciliation based on reference numbers and clear suspense accounts before the period is closed.
- Separate UAT and production environments, then retest all reports with 12 months of data before go-live.
FAQ
Does this automated system guarantee that my reports comply with PSAK?
Not automatically. Compliance is determined by the accounting policies, recognition, and measurement you choose, as well as the completeness of the notes to the financial statements. The system only ensures consistency, traceability, and that every transaction is recorded in the correct account.
How long does implementation take for one entity?
For one entity with about 120 accounts and integration with an existing SIMRS or POS, the COA design and mapping phase takes 2 weeks, development and opening balance migration take 3–4 weeks, then parallel run takes 2 weeks. Total 6–8 weeks until the first full close of books.
Do I have to replace the current SIMRS or ERP?
No. The accounting layer can be built separately as an accounting service that receives events from SIMRS, ERP, or POS via API and message queue. This approach is cheaper and does not disrupt daily operations.
How should rejected or revised BPJS claims be handled?
Record rejected claims as reversing journals for receivables and revenue in the period of the decision, then create an allowance based on estimated recovery. Do not delete the original claim journal so the verification trail and potential re-claim remain visible.
Building automated financial statements that comply with PSAK is not a one-time project but an operational discipline: a tidy COA, idempotent journals, scheduled book closing, and assertions that keep figures consistent. If you manage a hospital, clinic, or distribution company in Yogyakarta, DIY, or Magelang and want to accelerate book closing from days to hours, our team is ready to help, from COA audit, SIMRS/SIM Klinik/ERP/POS integration, to website and audit-ready financial dashboard development. Contact Nugroho Setiawan for an IT consulting session and mapping of your system requirements.
This article is part of the knowledge base of Nugroho Setiawan, an IT consultant in Jogja (Yogyakarta). Related solutions: PilarPOS SIMRS (Hospital Information System) BPJS & SatuSehat Integration. Need a free consultation?
Komentar
Belum ada komentar. Jadilah yang pertama!
Saya membantu konsultasi IT & pengembangan aplikasi untuk rumah sakit, klinik, dan bisnis di Yogyakarta (DIY), Magelang, dan sekitarnya.