Akuisisi PT (Pengambilalihan) Flow — Design Spec
Date: 2026-07-01
Status: Approved design, pending spec review
Author: Efran Nathanael (with Claude)
Related: docs/research/sabh-pt-untouched-flows-2026-07-01.md (roadmap, Akuisisi = #1), docs/superpowers/specs/2026-07-01-akta-transaction-type-classifier-design.md (the classifier this consumes), build map at https://x056.think.val.id/akuisisi-build-map-2026-07-01.md, memory project_pt_transformation_roadmap.
1. Overview & Goal
Akuisisi PT (Pengambilalihan, SABH id_aksi=19) is the first roadmap flow after the akta transaction-type classifier. It is a perubahan-family transaction: an akta approving a control-changing share transfer in a registered PT, plus a mandatory newspaper announcement (pengumuman). The user uploads the akta pengambilalihan (+ supporting docs); we OCR it, look up the PT in SABH, diff the mutated cap table against the registry, cross-validate, review per-section, and submit — reusing the existing PERUBAHAN_PT machinery almost wholesale.
Goal: ship a working AKUISISI_PT OCR flow that reaches parity with the perubahan flow's review/verify/finalize experience, adds the one acquisition-defining net-new (the pengumuman), and captures the SABH legal-path fields as lightweight attestation — without re-implementing the perubahan pipeline.
Single most important fact: SABH's akuisisi is single-subject — the acquirer is just a name string on the target PT's record, not a second entity to resolve. Do NOT model a two-party acquisition. It is "Perubahan PT with the share transfer forced-on and a pengumuman required."
2. Scope
In scope (phase 1)
- New AKUISISI_PT SubmissionType with a thin-fork processor reusing all shared perubahan services.
- The Phase-B classifier wiring that lights up AKTA_AKUISISI routing (deferred from the classifier build).
- Hybrid extraction: reuse the perubahan akta extractor for the cap-table core + a focused pass for the pengumuman (koran name/date).
- Pengumuman: extract + require tanggal_koran/nama_koran (mirror SABH); a 30-day pre-RUPS window as an advisory WARNING.
- Attestation-lite: the Tertutup/Terbuka + langsung/melalui-Direksi + deed-type radios, persisted for submit-back, no deep validation.
- Forced-on peralihan_saham (the definitional change) through the existing diff/roster.
Out of scope (deferred — §9)
- A dedicated AKTA_AKUISISI_SCHEMA + akta_subtype (phase-2 cleanup; the perubahan extractor already yields the cap table).
- The full validated legal-path tree (OJK approval, Rancangan Pengambilalihan document requirements, deed-type sub-branch validation).
- The 14-day creditor-objection window as a hard gate (advisory/lifecycle, not a client gate).
- Any dual-entity acquirer↔target modeling.
- The SABH submit-back REST API (out of scope for the whole PoC until the integration project starts).
3. Current Context (what we reuse)
The PoC already ships PERUBAHAN_PT, a doc-first flow: klasifikasi → perubahan-processor → SABH company-lookup + oldData (sabh-pt-snapshot) → perubahan-diff (per-jenis) → PerubahanSectionApproval review → verifikator (MENUNGGU_VERIFIKATOR → VOTING → SELESAI) → finalize. Akuisisi is a member of the same SABH family (id_aksi 10/19/11 share the linkPerubahanPerseroan path), so the machinery transfers.
Reuse-as-is (no change):
| Component | Path | Role for Akuisisi |
|---|---|---|
| Diff primitives | backend/src/services/perubahan-diff.ts (diffPeople/diffTable/diffScalar/diffCurrency/isFullReplacementDeed) |
Same cap-table diff |
| Acquisition math | backend/src/services/post-perubahan-roster.ts (composePostRoster/applyPeralihan) |
applyPeralihan (subtract from dari, add to kepada, recompute %) is the acquisition |
| SABH lookup + old data | backend/src/services/company-lookup.ts, sabh-pt-snapshot.ts |
Locate the target PT + snapshot |
| Akta schema | backend/src/schema/akta-perubahan.ts (PeralihanSaham[], pemegang_saham_lama/baru, detail_pemegang_saham, direksi/komisaris lama/baru, KBLI) |
The cap-table payload |
| Cross-validator | backend/src/services/cross-validator.ts (validateSharesTransferBalance, identity/modal/KBLI/OLD_DATA_CONSISTENCY) |
Same validations |
| Review assembler | backend/src/services/load-diff-input.ts + routes/perubahan.ts GET review-data |
Delta review data |
| Change-type detection | backend/src/services/change-type-detector.ts |
jenis-perubahan detection (pure fn) |
| Verifikasi state machine | PerubahanSectionApproval / attestation / VerifikasiPerubahan tables + verifikator flow |
Shared (identical shape) |
| Frontend review/verify/vote | PerubahanDeltaReviewPage, CompanySelectionModal, VerifikatorReviewPage, PublicVotingPage, use-perubahan hooks |
Mirror for akuisisi routes |
The classifier is already built: AKTA_AKUISISI emits end-to-end (title rule + refineAktaLabel override). It is blocked only by akuisisi ∉ BUILT_AKTA_TXN_TYPES and the create-gate treating it as unsupported. Enabling routing is part of this flow.
4. Design
4.1 SubmissionType + processor (thin fork)
Add AKUISISI_PT to the Prisma SubmissionType enum. Create backend/src/services/akuisisi-processor.ts as a thin fork of perubahan-processor.ts — same Phase 1–5 shape, delegating to the shared services above. The fork exists because (a) SABH stamps a distinct id_aksi=19, (b) the dispatchProcessor + reMatchAndValidate exhaustiveness switches force a per-type branch, and (c) akuisisi forces peralihan_saham on and requires the pengumuman — rules a PERUBAHAN_PT flag would smuggle through perubahan-assuming guards. Keep the fork thin: it should be mostly "call the shared perubahan service, plus the akuisisi deltas (forced peralihan, koran, attestation)."
Route module backend/src/routes/akuisisi.ts mirrors routes/perubahan.ts (company-select, review-data, section-approval, finalize), reusing the same handlers where the shape is identical.
4.2 Data flow
upload akta pengambilalihan (+ KTP/NPWP of new pengurus/PS if present)
→ klasifikasi: x056 coarse AKTA_PERUBAHAN → refineAktaLabel → AKTA_AKUISISI
→ create AKUISISI_PT submission (gate now allows it)
→ EXTRACT (hybrid, §4.3): perubahan extractor for cap-table core + focused koran pass
→ SABH company-lookup + oldData snapshot
→ perubahan-diff + applyPeralihan (peralihan_saham forced-on)
→ cross-validate (+ AKUISISI_KORAN_WINDOW advisory)
→ per-section delta review (PerubahanSectionApproval)
→ verifikator (MENUNGGU_VERIFIKATOR → VOTING → SELESAI)
→ finalize (allow-list admits AKUISISI_PT)
4.3 Extraction (hybrid)
The deed is classified AKTA_AKUISISI, but extraction reuses the perubahan extractor: map AKTA_AKUISISI → akta_subtype 'perubahan' for the GPU extraction call, via the existing classification-override path in backend/src/services/document-processor.ts (the same mechanism that already lets Step-2 re-mapping override re-classification). The perubahan extractor populates peralihan_saham + pemegang_saham_lama/baru directly from a pengambilalihan deed (confirmed by the akta-data-fit reader — share-transfer clauses are structurally identical).
Net-new extraction: a focused grounded pass to pull the pengumuman (tanggal_koran, nama_koran) — these have no fields in the perubahan schema today. Model it like the apostille grounded single-field recovery (a small targeted LLM call on the deed/pengumuman text, not a broadening of the shared prompt). A fully dedicated AKTA_AKUISISI_SCHEMA is deferred (§9); the routing branch, not the schema, is the real blocker.
4.4 Pengumuman validation
- Required fields (mirror SABH):
tanggal_koran+nama_koran(repeatable). The flow requires at least one koran entry forAKUISISI_PT. AKUISISI_KORAN_WINDOW— advisory WARNING, not FAIL: ideally the koran announcement is ≥30 days before the RUPS (UUPT Pasal 127 ayat 2). But Pasal 127 ayat 8 exempts a direct-from-shareholder acquisition (the Tertutup/langsung route) from the pre-announcement regime — so a hard FAIL would wrongly flag legitimate direct acquisitions. Emit a WARNING whentanggal_koran > tanggal_rups − 30d, never a FAIL. (Follows the intentional-WARNING precedent inproject_pp_nik_age_advisory.)
4.5 Attestation-lite (legal path)
Persist the SABH legal-path selections as a small structured attestation on the submission (akuisisiAttestation Json?): jenisPerseroan (TERTUTUP/TERBUKA), caraPengambilalihan (LANGSUNG/MELALUI_DIREKSI), deedType (AKTA_NOTARIS/DI_BAWAH_TANGAN). Surface as simple radios in a review step. No conditional required-document logic, no OJK/Rancangan validation. Purpose: preserve the SABH submit-back data contract so these fields aren't re-entered later.
4.6 Control-change / forced peralihan
peralihan_saham is the definitional change of an akuisisi, so the flow forces it on in change-type detection (SABH checks+hides it in step 4). The resulting roster flows through applyPeralihan unchanged. A control-threshold (>50% pengendali) flag is not required in phase 1 — SABH self-declares control change; we capture the share transfer + attestation and leave the control determination to the notary's attestation.
SABH parity — single subject (hard boundary): match SABH exactly — the acquirer is captured ONLY as a row in the target's new pemegang_saham roster (a name; if a badan hukum, its jenis + optional nomor-akta/SK already carried by the schema). It is NEVER resolved against the registry, linked as a separate entity, or modeled as a target↔acquirer relationship. From the target PT's record an akuisisi is a peralihan saham; do not build a two-party resolver (that would diverge from SABH, which is single-subject). Multi-entity resolution is a Merger/Peleburan concern, not this flow.
5. Integration / wiring checklist (Phase-B plumbing)
Each is mechanical (~1 change), but all are required — several are compile-time-enforced:
1. backend/prisma/schema.prisma — add AKUISISI_PT to enum SubmissionType; prisma generate.
2. backend/src/services/akta-txn-classifier.ts — add akuisisi to BUILT_AKTA_TXN_TYPES (routes it; removes it from detectUnsupportedAktaType).
3. backend/src/routes/submissions.ts — inferSubmissionType: AKTA_AKUISISI → AKUISISI_PT.
4. backend/src/services/submission-dispatch.ts — processorKindForType + dispatchProcessor case (satisfies the const _exhaustive: never tripwire → compile fails until added).
5. backend/src/services/submission-processor.ts — reMatchAndValidate branch → reMatchAndValidateAkuisisi (the validation-wipe gotcha — [[reference_rematch_dispatch]]).
6. backend/src/routes/klasifikasi.ts — AKUISISI_DOC_TYPES label set + serve ?type=akuisisi.
7. backend/src/services/document-processor.ts — AKTA_AKUISISI → akta_subtype 'perubahan' extraction routing + trigger the koran pass.
8. Frontend — lib/classifier-labels.ts (display name), components/klasifikasi/detect-banner.tsx (DetectedFlow + FLOW_LABELS/FLOW_DESCRIPTIONS), pages/KlasifikasiPage.tsx (detectedFlow + createSubmission.onSuccess route), routes.tsx (akuisisi routes), lib/destination-for.ts/flow-steps.ts.
9. Finalize allow-list — admit AKUISISI_PT in the finalize gate.
6. Data model / persistence
- Reuse (perubahan fields on
Submission):companyLookupResult,oldData,changeTypeResult,selectedJenisPerubahan,selectedNomorTransaksi,selectedTahun. - New:
akuisisiAttestation Json?onSubmission(§4.5). Koran data lives with the extracted akta payload (ExtractedField/ the akta JSON), aggregated for validation like other extracted fields. Additive migration only — no enum/table removals. - Shared:
PerubahanSectionApproval+ verifikasi tables (no schema change; keyed by submission).
7. Error handling & gotchas
reMatchAndValidateMUST dispatchAKUISISI_PTor field edits silently wipe validations ([[reference_rematch_dispatch]]) — usequeueReMatchAndValidate(serialized), never fire-and-forget.- Per-jenis sections need EXPLICIT persisted
PerubahanSectionApproval— the submit gate reads it; never derive section-approved from child state ([[reference_perubahan_section_approval]]). - The two
never-switches (dispatchProcessor, and any other exhaustiveSubmissionTypeswitch) will not compile untilAKUISISI_PTis handled — add all cases before relying on green. - Koran window is WARNING-only; do not let it block the step-2 gate or auto-FAIL.
- Extraction override: ensure the stored
classifiedType = AKTA_AKUISISImaps toakta_subtype 'perubahan'at extraction (not a fresh x056 re-classify that could disagree).
8. Testing strategy (TDD)
Mirror the perubahan test suites; DB tests target ahu_ocr_test ([[feedback_test_db_isolation]]).
- Routing/wiring: AKTA_AKUISISI → AKUISISI_PT; no longer "unsupported"; reMatchAndValidate/dispatchProcessor route to the akuisisi processor (not the default akta path that wipes validations).
- Acquisition math: applyPeralihan share-transfer balance for an akuisisi roster (reuse validateSharesTransferBalance cases).
- Pengumuman: koran extraction present/required; AKUISISI_KORAN_WINDOW emits WARNING (never FAIL) inside and outside the 30-day window; exempt-path (direct) still passes.
- Attestation: akuisisiAttestation persists + round-trips; no conditional-doc validation fires.
- Regression: existing PERUBAHAN_PT routing/diff untouched.
9. Deferred (phase 2 / out of PoC scope)
Dedicated AKTA_AKUISISI_SCHEMA + akta_subtype; full validated legal-path tree (OJK, Rancangan doc requirements, deed-type validation); 14-day creditor-objection hard gate; dual-entity acquirer modeling; the SABH submit-back API.
10. Open questions / risks
- Koran extraction reliability. A pengambilalihan deed may state the pengumuman inline, or the koran may be a separate clipping we're not ingesting. Phase 1 extracts what's in the deed text; if absent, the field is left for manual entry (required-but-empty → review), not an OCR failure. Confirm on real deeds during implementation.
- Does the perubahan extractor reliably populate
peralihan_sahamfrom a real pengambilalihan deed? The akta-data-fit reader says yes (structurally identical clauses); verify on a sample akta pengambilalihan before committing the hybrid (fall back to the dedicated schema sooner if it doesn't). - Shared vs forked verifikasi tables. Design assumes the
PerubahanSectionApproval/verifikasi shape is identical for akuisisi. If any akuisisi-specific section (attestation, koran) needs its own approval row, add it as a section within the shared table rather than a parallel table.