Collect, verify, store and report every activity carried out under PETRA MoUs and MoAs.
From agreements on paper to real activities.
The working app, on demo data: the dashboard, the activity list and the single-page report form.
SIM Realisasi
The working app, running on demo data. It loads here, inside the page.
Demo data generated over the real SIM Kerjasama agreements and units; not yet real activities.
An agreement is only worth what is carried out under it. Until now, nobody could count that reliably.
Collect each activity carried out under a PETRA MoU or MoA, linked to that exact agreement.
Mobility activities are checked by the IO Mobility team; only verified data reaches the numbers.
RENSTRA 1.1, 1.19.S1 and 1.19.S4 and the International Awards come straight from the data.
Frozen semester snapshots make every report reproducible.
Feed activity back to SIM Kerjasama so dormant partnerships surface.

Calculated from verified data, with drill-down to every activity.

Built from frozen, reproducible snapshots.

Made visible, with that information fed back to SIM Kerjasama.
Included: Activities that implement a PETRA MoU or MoA recorded in SIM Kerjasama.
Not included: IISMA, free movers, government scholarships, and imports of historical data.
Modelled in BPMN: one process follows an activity through verification; the other covers what runs on a schedule.
One activity, from the unit that ran it to the indicators it counts toward.
Semester and academic year come from the start date; the deadline is the end date plus 30 days. Late submissions are flagged, not refused.
A wrong submission always comes back as a revision with a note, and returns as participant version n+1.
If two units report the same student, the Mobility team picks the activity that keeps them. Approval waits until it is decided.
Drafts can be deleted by their creator or IO Admin. A submitted activity is never deleted.
What the system does on its own schedule, and what leadership receives.
Drafts close to their deadline, and revisions open seven days or more, get an in-app and email reminder.
On the cutoff date the daily job freezes the semester's indicators into a snapshot with every record ID behind them. IO Admin can only re-freeze, with a reason.
An activity verified or edited inside a frozen window is logged as a post-freeze change, not merged silently.
Leadership reads snapshots and live year-to-date figures side by side, and downloads them as Excel.
Details. Pick the agreement valid on the activity date.
Enforced in the database with row-level security, not only hidden in the interface. Every export with student data is logged (UU PDP 27/2022).
| Action | Academic unit | IO Mobility | IO Admin | Leadership |
|---|---|---|---|---|
| Create, submit and revise activities | Own unit | No | On behalf of a unit | No |
| See activities | Own and co-unit | All | All | Verified only |
| See participant names and mobility PDFs | Own | Yes | Yes | No |
| Verify mobility, decide duplicate students | No | Yes | Yes | No |
| Edit a verified activity (logged) | No | Participants | Yes | No |
| Settings, calendar, activity-type rules, freeze | No | No | Yes | No |
| Dashboard, awards, non-personal Excel | Own unit | Yes | Yes | Yes |
| Excel with student data (logged) | Own unit | Yes | Yes | No |
Tap a card.
Tap a card.
Transcribed from the source repository: migrations, functions, README and rules. The database owns the business rules; the app calls into it.
Realisasi lives in schema realisasi beside SIM Kerjasama and only reads it through views in schema kerjasama. It never writes SIMKS tables.
Status derivation, period derivation, chain resolution and KPI math are SQL, so the UI, exports and snapshots can never disagree.
BAAK and HR are mocked, but the app only calls lookup_students and lookup_employees, so production swaps the implementation, not the app.
Snapshots are written once; a correction creates a new superseding snapshot with a reason.
Participant data and transcripts sit behind RLS and private storage; every personal-data export is logged.
| Layer | Choice |
|---|---|
| Frontend | Next.js 15 App Router, TypeScript, Tailwind, shadcn/ui |
| Forms | react-hook-form + zod, schemas shared client and server |
| Data | Supabase Postgres, RLS, SQL functions, @supabase/ssr |
| Auth | Supabase Auth, shared with SIM Kerjasama; demo role switcher |
| Files | Supabase Storage, private buckets, 5-minute signed URLs |
| Jobs | pg_cron for reminders and freezes; triggers for the duplicate scan |
| Excel & charts | exceljs in route handlers (streamed), Recharts |
| Tests | SQL acceptance tests, Vitest, Playwright |
Pick a step: the functions it calls, the tables it writes, the status it sets and the rules that guard it. The touched tables light up in the ERD.
The activity, its units and one agreement link (the original document plus its chain_id). Partner name and country are snapshotted by trigger.
documents_valid_between()save_activity_draft()activitiesactivity_unitsactivity_documentsactivity_partner_snapshotdocumentsRules: R-03, R-04, R-05, R-06
Highlighted in amber: activities, activity_units, activity_documents, activity_partner_snapshot, documents.
Refreshes stored chain ids (SIM Kerjasama may re-parent documents), then scans drafts and open revisions.
run_daily_jobs()_mark_once()activity_documentsjob_marksHighlighted in amber: activity_documents.
The core of the SIM Realisasi schema, grouped by domain. Select a table to trace its relationships.
Select a table to highlight its relationships. Scroll the diagram in both directions.
kerjasama.units
id (PK), name, parent_id, is_academic, +2
kerjasama.agendas
id (PK), name, is_active
kerjasama.documents
id (PK), doc_number, kind, status, start_date, end_date, auto_renewed, predecessor_id (FK*), +5
kerjasama.partners
id (PK), name, country_code (FK*), merged_into_id, +1
kerjasama.countries
code (PK), name, is_domestic, +2
activity_units
activity_id (PK), unit_id (PK), is_submitter
activity_documents
activity_id (PK), original_document_id (PK), chain_id, out_of_scope_warning
activity_partner_snapshot
id (PK), activity_id (FK), partner_id (FK*), partner_name, country_code, +2
activity_files
id (PK), activity_id (FK), kind, version, storage_path, is_current, +6
activities
id (PK), code (UQ), agenda_id (FK*), direction, start_date, end_date, academic_year_id (FK), semester_id (FK), status, mobility_status, reporting_deadline, is_late, verified_at, +13
participant_set_versions
id (PK), activity_id (FK), version, status, review_note, +4
participant_students
id (PK), set_version_id (FK), section, nrp (FK*), prodi_name, home_institution, +4
participant_staff
id (PK), set_version_id (FK), employee_id (FK*), full_name, +1
participant_conflicts
id (PK), nrp, activity_a (FK), activity_b (FK), status, kept_activity_id (FK), +4
agenda_rules
agenda_id (PK), mobility_category, counts_for_s1, +2
academic_years
id (PK), label (UQ), start_date, end_date
semesters
id (PK), academic_year_id (FK), term, cutoff_date, +2
kpi_snapshots
id (PK), academic_year_id (FK), kind, values, settings_used, superseded_by (FK), refreeze_reason, +6
kpi_snapshot_items
snapshot_id (FK), kpi_code, ref_id, is_late_addition, +2
activity_log
id (PK), activity_id (FK), kind, action, diff, in_frozen_period, +4
mock_baak.students
nrp (PK), full_name, prodi_name, category, status, +5
mock_hr.employees
employee_id (PK), full_name, unit_name, status, +1
notifications
id (PK), recipient_id, kind, read_at, +4
email_outbox
id (PK), to_email, subject, sent_at, +2
export_log
id (PK), actor_id, export_kind, row_count, contains_personal_data, +2
FK* = logical reference to a view or registry, checked by a function because views cannot be foreign-key targets.
| ID | Rule | Enforced by |
|---|---|---|
| R-03 | An activity links to exactly one agreement. The link stores the original document; the current one is always resolved through the renewal chain. | activity_documents + chain_current |
| R-04 | An agreement is selectable if its validity overlaps the activity dates, whatever its status today. Rejected and in-process documents never are. | documents_valid_between |
| R-06 | Partner name and country are snapshotted at link time; later edits or merges in SIM Kerjasama do not change them. | Trigger |
| R-09 | Semester and academic year are derived from Tanggal Mulai; a date outside any configured year cannot be submitted. | Trigger |
| R-10 | Reporting deadline = Tanggal Selesai + reporting_deadline_days (default 30). Late submissions are accepted and flagged. | Trigger |
| R-11 | A mobility kegiatan needs participants and one mobility PDF; its track starts pending. Any other kegiatan is verified on submit. | submit_activity |
| R-16 | Every counted student must have an NRP that resolves in BAAK. Unknown NRPs block submission. | lookup_students |
| R-21 | Participant sets are versioned. Only the single approved version counts; approving a new one supersedes the previous. | Trigger |
| R-25 | Overall status is derived, never set directly: draft, revision_requested, verified or in_verification. | _trg_activity_status |
| R-26 | Nothing is rejected: a wrong submission goes back to the unit as a revision. | RPC |
| R-27a | Mobility cannot approve while the kegiatan has open duplicate-student conflicts. | mobility_approve |
| R-33 | Same NRP in two non-draft mobility kegiatan of different submitting units with overlapping dates is a conflict. | _scan_conflicts |
| R-36 | Only verified activities count. Activity date = Tanggal Mulai. | compute_kpis |
| R-42 | Realisation of agreements counts renewal chains, so a mid-year renewal counts once. | compute_kpis |
| R-43 | The denominator excludes chains starting within grace_period_months (default 6) of the cutoff; auto-renewed chains are always included. | compute_kpis |
| R-55 | On each semester cutoff the system freezes a snapshot: Ganjil → ganjil_ytd, Genap → genap_full_year. | run_daily_jobs |
| R-56 | A snapshot stores values, contributing IDs and the settings used. Settings changes never alter existing snapshots. | kpi_snapshots |
| R-63 | Every export containing personal data is logged with actor, filters and row count. | log_export |
| R-64 | No hard deletes after submission; everything is status plus log. | RLS + RPC |
activity_statustrack_statuspset_statusmobility_categoryfile_kindsnapshot_kindconflict_statusdirectionrealisasi-daily-jobs → run_daily_jobs()pg_cron 18:00 UTC (01:00 WIB)Refresh chain ids, revision and deadline reminders, semester freezesDuplicate scanTrigger, on submit and every participant changeOpen or close participant_conflicts