think
16px
820px

PP Perubahan & Perbaikan — Flow Alignment Plan

Date: 2026-07-30
Status: Planning only — no code changed. For review before implementation.
Sources of truth: docs/user_guide_perubahan_ptp.pdf (v1.4, 43 pp) · docs/perbaikan_data_ptp.pdf (v1.2, 9 pp)
Scope note: Voucher/payment steps excluded per instruction. Focus = flow correctness for Perubahan & Perbaikan.


1. What the official guides actually say

1.1 Perbaikan Data (guide §2.1)

"Perbaikan data diperuntukan bagi pemohon yang melakukan kesalahan pengetikan atau pemilihan data yang terdapat pada transaksi terakhir yang dilakukan, baik transaksi pendirian ataupun perubahan."

Perbaikan = correct the latest transaction (whichever it was — pendirian or perubahan).

flowchart TD A[Menu Perbaikan] --> B[Pilih Nama Perseroan
Nomor Sertifikat auto-fill] B --> C[Form Perbaikan
pre-filled from LATEST transaction] C --> D[Pratinjau: Data Perseroan lama
vs Data Perseroan Terbaru] D --> E[Centang Persyaratan + Submit] E --> F[Konfirmasi popup: Ya, Saya Yakin] F --> G[Detail Transaksi +
Unduh Surat Pernyataan Perbaikan]

Editable field set (Gambar 73–74):
- Data Perseroan: Email, Alamat, RT, RW, Kelurahan, Kecamatan, Kabupaten/Kota, Provinsi, Kodepos, No. Telepon
- Pemilik Usaha: Nama Lengkap, No. Telepon, Tempat Lahir, Tanggal Lahir, Alamat, RT, RW, Kelurahan, Kodepos
- Locked/display-only: NIK, NPWP, Email pemilik, Jabatan, Kecamatan/Kabupaten/Provinsi pemilik
- NOT present in Perbaikan: Modal Usaha, Kegiatan Usaha (KBLI), Nama Perseroan, Pemilik Manfaat (BO)
- No "pilihan/section" step — the form opens directly, pre-filled.

Deliverable: Surat Pernyataan Perbaikan (Gambar 79) — restates corrected data, QR, "disetujui Pemohon secara elektronik."

1.2 Perubahan Data (guide §2.1.1)

flowchart TD A[Menu Perubahan] --> B[Pilih Nama Perseroan
Sertifikat + NPWP auto-fill] B --> C[Halaman Pilihan Perubahan] C --> D[Form Pendaftaran Perubahan
fields open per selection] D --> E[Centang Persyaratan + Submit] E --> F[Pratinjau: Yakin dan Submit Permohonan] F --> G[Popup Submit Berhasil
batas konfirmasi 7 hari] G --> H[Daftar Transaksi:
Konfirmasi Pernyataan Perubahan] H --> I[Detail: Preview / Ubah Data /
Konfirmasi / Hapus Transaksi] I --> J[Konfirmasi popup: Ya, Saya Yakin] J --> K[Unduh Pernyataan Perubahan +
Cetak Sertifikat]

Pilihan Perubahan (Gambar 3–4):
- Data Perseroan (checkboxes, multi): Nama, Email, Alamat, Kegiatan Usaha, Modal Usaha
- Data Kepemilikan (radio, pick ONE): Pemilik Usaha or Peralihan Usaha (mutually exclusive)
- Includes full Pemilik Manfaat management (kriteria a–g popup, WNI/WNA forms)

Deliverables: Surat Pernyataan Perubahan ("PERUBAHAN PERNYATAAN PERUBAHAN PERSEROAN PERORANGAN", Gambar 59) + Sertifikat Perubahan (Gambar 60).

1.3 Perubahan disertai Peralihan Pemilik Usaha (guide §2.1.2)

A two-account ownership transfer (distinct from our PP→PT "peralihan"):
1. Current owner picks "Peralihan Usaha", fills Calon Pemilik Usaha Baru (new owner must already have an account), submits, confirms.
2. New owner logs into their own account → Daftar Transaksi → Detail → Terima / Tolak the transfer → then Unduh Surat Pernyataan + Cetak Sertifikat.

1.4 The full Surat Pernyataan set (post-transaction deliverables)

Transaction Surat Pernyataan title System-generated? Confirmation gate
Pendirian PERNYATAAN PENDIRIAN PP yes at submit
Perubahan PERUBAHAN PERNYATAAN PERUBAHAN PP yes separate Konfirmasi, 7-day window
Perbaikan PERNYATAAN (pendirian-style layout) yes at submit
Peralihan uses Perubahan pernyataan yes current owner + new owner Terima/Tolak

Common to all: A-mark/Pengayoman logo, Data Perseroan + Data Pemilik Usaha blocks, QR code, "Surat Pernyataan telah disetujui Pemohon secara elektronik."


2. Current app state (verified in code)

Generation infra already presentdocxtemplater, pizzip, pdf-lib installed; existing generators:
- src/services/sp-pendirian-pp-generator.ts → Surat Pernyataan Pendirian PP from templates/surat-pernyataan-pendirian-pp.docx
- src/services/surat-keterangan-generator.ts → Surat Keterangan Perbaikan PT (not PP)
- Download routes for blank templates (sp-pendirian-pp.ts, pp-pendirian.ts, surat-keterangan.ts)
- src/llm/surat-pernyataan-extract.ts handles the inbound (OCR-extract an uploaded surat), which is a different direction.

Perbaikan PP flow (verified earlier, verdict B):
- POST /pp/perbaikan-pp/startlookupPpById() reads the current registry snapshot, NOT the latest transaction. No query for the last Perubahan/Perbaikan.
- Uses a section picker borrowed from perubahan (PERBAIKAN_SECTION_KEYS); CARRIER_EDITABLE_FIELDS includes modal — which the real Perbaikan form does not allow.
- No priorSubmissionId / transaction-chain link in schema.prisma.
- Frontend shows before/after but no statement of what Perbaikan is or that fields come from the latest transaction.


3. Gap analysis

# Area Guide says Our app Severity
G1 Surat Pernyataan output Perubahan & Perbaikan each generate a downloadable surat Only Pendirian PP generator exists High (pinpoint #1)
G2 Perbaikan data source Corrects the latest transaction (pendirian/perubahan) Reads current registry snapshot; no transaction chain High (pinpoint #2)
G3 Perbaikan UI intent Clear "correct your last request" framing No explanatory info surfaced Medium (pinpoint #2, FE-first opportunity)
G4 Perbaikan editable scope contact + address + owner personal data only Includes modal; section picker; may expose BO Medium
G5 Perbaikan has no section picker Form opens directly, pre-filled We show a section-selection step Medium
G6 7-day two-phase Konfirmasi Submit → separate Konfirmasi (Ubah/Konfirmasi/Hapus) → Sertifikat Partial confirmation concept from PR #7; not the 7-day two-phase model Needs decision
G7 Peralihan Pemilik Usaha (2-account) Ownership transfer w/ new-owner Terima/Tolak Not implemented (our "peralihan" = PP→PT conversion, different) Needs decision (likely out of scope)

4. The two pinpoints, elaborated

Pinpoint #1 — Surat Pernyataan set (G1)

Real flow produces a distinct Surat Pernyataan per transaction type, delivered after the transaction. We already do this for Pendirian; we're missing Perubahan and Perbaikan generators for PP. The docxtemplater pattern in sp-pendirian-pp-generator.ts is directly reusable.

Action shape: confirm the canonical set → author .docx templates for Perubahan & Perbaikan → add generators mirroring the pendirian one → expose a post-completion download (mirroring how Pendirian's surat is offered).

Pinpoint #2 — Perbaikan reliability & FE representation (G2/G3)

Two layers, decoupleable exactly as you noted:
- FE-first (cheap, high value): the frontend can represent the intent now — a header/explainer ("Perbaikan memperbaiki transaksi terakhir Anda"), show which transaction is being corrected, and scope the editable fields to the real Perbaikan set — even before the backend truly resolves "latest transaction." This makes the flow legible immediately.
- BE-true (deeper): resolve the actual latest COMPLETED transaction for the PP, seed the form from it, add a priorSubmissionId link, and (optionally) scope editable fields to that transaction's changed set.


5. Locked scope (decided 2026-07-30)

# Decision Choice
Q1 Surat Pernyataan scope Surat Pernyataan + Sertifikat bothrevised 2026-08-02: the Sertifikat is Perubahan-only (guide Gambar 60); Perbaikan gets only the Surat (Daftar Transaksi Gambar 78 shows no Cetak Sertifikat).
Q2 7-day two-phase Konfirmasi Deferred — but expected soon; design must not preclude it
Q3 Perbaikan FE/BE order Both together (FE representation + BE latest-transaction in one pass)
Q4 Peralihan Pemilik Usaha (2-account) Deferred — but a probable near-future change; keep the door open

5.1 Finalized phased plan

Phase 1 — Perbaikan flow correctness (pinpoint #2, FE+BE together)

  • BE: lookupPpById (perbaikan path) → resolve the latest COMPLETED PENDIRIAN/PERUBAHAN/PERBAIKAN for the PP; seed oldData from that transaction; add priorSubmissionId to Submission + migration.
  • FE: intent/explainer ("memperbaiki transaksi terakhir Anda"); label which transaction is being corrected; correct editable-field scope (drop Modal/KBLI/BO; align to §1.1 set); remove/soften the section picker.
  • Forward-compat (Q4): model priorSubmissionId as a generic transaction-chain link (not perbaikan-only) so a future ownership-transfer chain can reuse it.

Phase 2 — Surat Pernyataan + Sertifikat generation (pinpoint #1)

Instead of cloning the notaris-authored template + docxtemplater pendirian pipeline, the new flows generate the surat directly from the finished submission (matches "provide the surat after the transaction is finished") via a shared services/sp-docx/blocks.ts (buildSuratDocument + renderDocxToFiles). Layout mirrors the pendirian reference (kept as-is per Efran 2026-07-31).

Done — Perbaikan surat vertical (2026-07-31):
- Surat identity (revised 2026-07-31): a Perbaikan does NOT emit a bespoke "Perbaikan" document — it regenerates the surat of the transaction it corrects (guide §2.1 / Gambar 79: fixing a Pendirian → corrected Pendirian surat; fixing a Perubahan → corrected Perubahan surat). Presentation is resolved by walking the priorSubmissionId chain (resolveBaseSuratPresentation); DATA still comes from the Perbaikan carrier. Data source and presentation are decoupled in sp-surat-data.ts.
- services/sp-docx/blocks.ts — shared surat builder + libreoffice PDF render.
- services/sp-perbaikan-pp-generator.ts — projects carrier corrections over the oldData snapshot.
- routes/pp-perbaikan-pp-surat.tsGET /:id/surat-pernyataan.pdf (gated on COMPLETED), mounted at the same base to keep the main route file under its ceiling.
- FE: KonfirmasiBanner gains a generic suratUrl "Unduh Surat Pernyataan" button; wired on the Perbaikan review page (COMPLETED only).
- Tests: generator (2) + route (3) + banner/form (green). libreoffice present ⇒ real PDF.

Done — Perubahan surat vertical (2026-07-31):
- Projection extracted to services/sp-surat-data.ts (shared by both flows — identical carrier field keys); sp-perbaikan-pp-generator.ts refactored + sp-perubahan-pp-generator.ts added as thin per-flow configs.
- Download unified into routes/pp-surat.ts — one type-dispatching router mounted at both flow bases (replaced the perbaikan-only router); keeps the two thin generators/router under the no-forks similarity threshold.
- FE: "Unduh Surat Pernyataan" button on the Perubahan review completed card.
- Hardened renderDocxToFiles with a per-conversion libreoffice UserInstallation profile (fixes concurrent-download / test-suite lock timeouts).

Done — Sertifikat vertical (2026-08-02, Perubahan-only):
- Scope correction: re-reading the authoritative guides, the Sertifikat exists only for Perubahan (Gambar 60 — "SERTIFIKAT PENDAFTARAN PERUBAHAN PERSEROAN PERORANGAN"). The Perbaikan Daftar Transaksi (Gambar 78) offers only "Unduh Surat Pernyataan Perbaikan" — no Sertifikat — because a Perbaikan is a data correction, not a re-registration event. Q1's "both flows" is therefore overridden to Perubahan-only (Efran-confirmed 2026-08-02).
- sp-docx/blocks.ts — added buildSertifikatDocument (ministry header → title → NOMOR → PT name + domicile → registration paragraph → Jakarta issuance + DJAHU signatory; garuda logo + verification QR omitted for lack of assets, as with the surat).
- sp-surat-data.ts — extracted a shared resolveCarrier helper (carrier-over-snapshot v()), now used by both the surat and the new buildSertifikatDataFromSubmission. Registration NOMOR falls back to the registry idPtp (AHU-issued number not in our snapshot).
- sp-perubahan-pp-generator.ts — added generatePerubahanSertifikatPp alongside the surat generator (output uploads/sp-perubahan-pp/<id>/sertifikat-perubahan.pdf).
- routes/pp-surat.ts — added GET /:id/sertifikat.pdf via a SERTIFIKAT_GENERATORS map (PERUBAHAN_PP only ⇒ Perbaikan returns 400); the surat + sertifikat handlers now share one serveDoc gate/stream helper.
- FE: "Unduh Sertifikat" button on the Perubahan review completed card (Perbaikan review unchanged — surat only).
- Tests: generator projection + docx heading + route 200/409 + Perbaikan-has-no-sertifikat 400 (guide fidelity). Backend 3028 pass / 2 pre-existing env fails; FE 1046 pass.

Remaining:
- Forward-compat (Q2): the download gate currently checks COMPLETED; when the Konfirmasi two-phase lands, swap to the single finalize-state check.
- Peralihan owner (Q4, deferred): the shared projection reads the owner from the carrier pemilik* fields; a jenis-7 Peralihan (new owner from an uploaded KTP) would need the KTP owner projection.

Phase 3 (deferred, design-ready) — Two-phase Konfirmasi & Peralihan Pemilik Usaha

Not built now. Phases 1–2 must leave clean seams:
- Konfirmasi (Q2): a submission finalizeState enum + 7-day expiry timestamp; Sertifikat issuance gated on it. Note: Submission.konfirmasiExpiresAt + KEDALUWARSA machinery already exists (built for Pembubaran PP + Perbaikan PP) — the future two-phase work extends it rather than starting fresh.
- Peralihan (Q4): reuse the priorSubmissionId chain; add new-owner account linkage + Terima/Tolak later.


7. Phase 1 — concrete task breakdown

De-risked scope (decided 2026-07-30). Phase 1 is now purely additive — nothing is deleted or re-scoped. The editable-field trim (old C1/C2/D2) is dropped from Phase 1; see Task 0. A1 runs in its light form. Revised Phase 1 = A1(light) + A2 + B1 + B2 + B3 + D1 + E1/E2/E3.

Goal: Perbaikan Data PP correctly (a) sources its pre-fill from the latest completed transaction of the PP, (b) records which transaction it corrects, and (c) tells the user so. FE + BE together (Q3).

Current-state anchors (verified)

  • Data source: backend/src/services/pp-company-lookup.tslookupPpById (L143) → mock path shapeFromCompletedPendirian (L212) builds the snapshot from the PENDIRIAN only, ignoring any later Perubahan/Perbaikan.
  • Start route: backend/src/routes/pp-perbaikan-pp.tsPOST /start (L341) stores oldData: snapshot + selectedJenisPerubahan: valid (L371). No prior-transaction link.
  • Section catalogue: backend/src/services/pp-perbaikan-pp-sections.tsPERBAIKAN_SECTION_KEYS (L42): 6 sections incl. modal, kbli, pemilik_manfaat; comment claims "Rebuild DTO parity".
  • Seeding/derive: backend/src/services/pp-perbaikan-pp-processor.tssnapshotScalarFields (L16, seeds modal), derivePerbaikanAfterExtraction (L104, branches on kbli/pemilik_manfaat/data_pemilik), reseedAndRevalidatePpPerbaikan (L296).
  • Schema: backend/prisma/schema.prismamodel Submission (L788): has oldData, selectedJenisPerubahan, konfirmasiExpiresAt; no priorSubmissionId.
  • FE picker: frontend/src/pages/pp/PpPerbaikanPpSelectPage.tsxSECTION_OPTIONS (L40, 6 options); boundary callout (L126); /start call (L110).
  • FE form/review: frontend/src/pages/pp/PpPerbaikanPpFormPage.tsx, PpPerbaikanPpReviewPageV2.tsx; hook frontend/src/hooks/use-pp-perbaikan-pp.ts.

Task 0 — RESOLVED: keep the 6-section catalogue; defer any trim

Guide v1.2 (§1.1) shows a narrow Perbaikan form (contact + address + owner, no picker); our code claims a broader Rebuild-API-parity set (6 sections + picker).
- Could not verify against Rebuild sourceahu-codebase/ahu-rebuild-ptp is not checked out in this environment.
- Evidence leans toward the guide being stale on scope: guide v1.2 uses 2021-era test data, while our code intentionally added Modal/KBLI/Pemilik Manfaat targeting the newer Rebuild.
- Decision: do not trim in Phase 1. Trimming would delete possibly-current capability and touch validators/processor/tests (the exact "break other parts" risk). Keep all 6 sections.
- Follow-up (cheap, non-code): confirm scope against the live Rebuild form at ptp.ahu.go.id, or check out ahu-rebuild-ptp and read its Perbaikan DTO. Only trim if that confirms the narrow set.

Workstream A — BE: latest-transaction data source (pinpoint #2 core) — LIGHT

  • A1 (light). Replace the mock path so pre-fill comes from the latest completed transaction, not always the founding Pendirian. Add resolveLatestPpTransaction(idPtp): pick the latest COMPLETED submission among PENDIRIAN_PP | PERUBAHAN_PP | PERBAIKAN_DATA_PP for the PP (order by updatedAt desc). Build the snapshot cleanly for the easy base cases (Pendirian, prior Perbaikan). pp-company-lookup.ts:212.
  • Deferred (not now): full reconstruction of a Perubahan submission's merged post-transaction state (oldData + confirmed edits). Only matters for mock/PoC demos; wire a TODO + fall back to the current pendirian-shaped snapshot for that case.
  • Real-registry mode (lookupPpByIdReal) already returns cumulative latest state — untouched.
  • A2. Return the resolved transaction's metadata (id, type, updatedAt, nomor/label) alongside the snapshot so the start route + FE can display it.

Workstream B — BE: provenance link + API exposure

  • B1. Add priorSubmissionId String? self-relation on Submission (named relation, e.g. PriorTransaction; add index). Keep it generic (not perbaikan-only) so the deferred Peralihan chain (Q4) can reuse it. schema.prisma:788. Apply via bunx prisma db push (PoC convention).
  • B2. In POST /start, set priorSubmissionId to the transaction resolved in A1. pp-perbaikan-pp.ts:371.
  • B3. Expose prior-transaction metadata in the perbaikan detail/review payload the FE already fetches (via use-pp-perbaikan-pp.ts).

Workstream C — BE: editable-scope reconciliation — DROPPED from Phase 1

Deferred per Task 0. No changes to PERBAIKAN_SECTION_KEYS, snapshotScalarFields, the processor branches, or PERBAIKAN_EXCLUDED. Revisit only after the Rebuild-form verification.

Workstream D — FE: intent (scope-trim dropped)

  • D1. Surface the corrected transaction on the Select + Form pages: e.g. "Memperbaiki transaksi terakhir: Perubahan · 26 Jul 2026 · No. …" — turns the flow legible (pinpoint #2's FE half). Uses B3 payload.
  • ~~D2. section-picker reconciliation~~ — dropped (Task 0).
  • D3. Update/extend FE tests touched by D1 as needed.

Workstream E — tests & verification

  • E1. BE unit: resolveLatestPpTransaction picks a later Perubahan over the Pendirian; falls back to Pendirian when none.
  • E2. BE route: /start stamps priorSubmissionId and seeds oldData from the latest transaction.
  • E3. Run the full backend + frontend suites before any push (not just typecheck).

Ordering & dependencies (de-risked)

flowchart LR A1["A1 light"] --> A2 --> B2 B1 --> B2 --> B3 --> D1 --> D3 A1 --> E1 B2 --> E2 D3 --> E3

A1 and B1 have no blockers — start immediately. Everything is additive; no deletions. Task 0's trim is deferred to a later, verified pass.

Implementation status (2026-07-30, awaiting review — no commit)

Task Status Where
B1 schema ✅ done schema.prismaSubmission.priorSubmissionId self-relation + index; prisma generate run
A1/A2 resolver ✅ done pp-company-lookup.tsresolveLatestPpTransaction + ppTransactionLabel
B2 stamp on start ✅ done pp-perbaikan-pp.ts POST /start
B3 expose in API ✅ done pp-perbaikan-pp.ts GET /:id/review-datapriorTransaction
D1 FE orientation ✅ done (relocated) Moved from the step-2 form to the entry page (PpPerbaikanPpSelectPage) — it's invariant orientation, belongs before the user commits. New GET /latest-transaction?idPtp= + usePpPerbaikanLatestTransaction feed the pre-start callout. Banner removed from PpPerbaikanPpFormPage. review-data.priorTransaction kept (persisted provenance, still useful on review/detail).
E1 unit test ✅ done / passing __tests__/pp-company-lookup-latest-tx.test.ts (4 tests)
E2 route test ✅ done / passing appended to __tests__/pp-perbaikan-pp.test.ts
DTO extraction ✅ done priorTransactionDto + PriorTransactionDto in pp-company-lookup.ts (keeps route thin — file-size fitness)
file-size ceiling ✅ updated routes/pp-perbaikan-pp.ts 1507→1516 (+9, documented)

DB: applied priorSubmissionId via prisma db push to both ahu_ocr_dev4 and ahu_ocr_dev4_test (the live ahu-ocr-dev-db container on :47030 — NOT the committed compose's :5433, which belongs to another project).

Verified: backend tsc clean · frontend tsc clean · frontend suite 1046 pass (142 files) · backend affected tests 49 pass / 0 fail (incl. E1/E2 + file-size fitness).
Known pre-existing failures (not from this work, both environmental): config-gateway (env has GPU_SERVER_DIRECT_URL set) and pp-pendirian-bo-persist (needs a live PaddleOCR service). Confirmed failing in isolation, independent of these changes.