think
16px
820px

Offer letter — the Indonesian block

Historical. Written while this was being designed; kept for the reasoning,
not as a description of what shipped. The current state — three templates, the
picker, and the punctuation bug this predates — is in OFFER_LETTERS.md.

Valid's offer letter becomes bilingual: Indonesian on top, English underneath,
one page, not side-by-side columns. The English is the existing approved wording
and is unchanged; the Indonesian mirrors it paragraph for paragraph.

Both blocks share the same {{UPPER_SNAKE}} merge fields, so a token must appear
byte-identically in both and Obscura fills every occurrence at render time.

Drafted, then reviewed by three independent passes — faithfulness to the English,
Indonesian employment-law register, and a from-scratch retranslation — and
reconciled. Not yet approved, and not yet in the .dotx.


1. THE FINAL INDONESIAN TEXT

No: {{NOMOR_SURAT}}
Jakarta, {{TANGGAL_SURAT}}

Kepada Yth.
{{CANDIDATE_NAME}}

Dengan hormat,

Dengan senang hati kami menyampaikan penawaran kerja untuk posisi
{{POSITION_TITLE}} ({{CONTRACT_TYPE}}, {{WORK_ARRANGEMENT}} - {{WORK_LOCATION}})
di Val.id. Mohon Anda meninjau ringkasan syarat dan ketentuan atas rencana
hubungan kerja Anda bersama kami berikut ini. Apabila Anda menerima penawaran
ini, tanggal mulai bekerja Anda adalah {{START_DATE}} atau tanggal lain yang
disepakati bersama.

Berikut syarat dan ketentuan kerja Anda, apabila penawaran ini Anda terima:

Posisi. Jabatan Anda adalah {{POSITION_TITLE}}, dan Anda akan bertanggung jawab
langsung kepada {{REPORTING_MANAGER}}.

Kompensasi Tunai. Perusahaan akan membayarkan gaji dasar bersih (net) sebesar
IDR {{NET_SALARY}} per bulan, dibayarkan sesuai jadwal penggajian standar
Perusahaan. Gaji tersebut tunduk pada penyesuaian sesuai kebijakan kompensasi
karyawan Perusahaan yang berlaku dari waktu ke waktu.

Manfaat Karyawan. Sebagai karyawan reguler Perusahaan, Anda memenuhi syarat
untuk mengikuti sejumlah program manfaat yang disediakan Perusahaan, antara lain
{{BENEFITS_LIST}}, yang diberikan sesuai kebijakan internal standar Perusahaan.
Selain itu, Anda berhak atas cuti tahunan dengan upah penuh selama
{{VACATION_DAYS}} hari kerja per tahun, sesuai kebijakan cuti Perusahaan.

Penerimaan. Anda dapat menyatakan persetujuan atas ketentuan ini dan menerima
penawaran ini dengan menandatangani serta membubuhkan tanggal pada perjanjian
ini selambat-lambatnya {{OFFER_EXPIRY_DATE}}. Setelah penawaran kerja ini Anda
terima, Val.id akan menyampaikan dokumen dan petunjuk yang diperlukan.

Bahasa. Surat ini dibuat dalam Bahasa Indonesia dan Bahasa Inggris. Apabila
terdapat perbedaan penafsiran di antara kedua naskah tersebut, naskah dalam
Bahasa Indonesia yang berlaku.

Kami menantikan kehadiran Anda sebagai bagian dari tim kami.

Hormat kami,
_________________________
{{SIGNATORY_NAME}}
{{SIGNATORY_TITLE}} - Val.id
_________________________
{{CANDIDATE_NAME}}
Kandidat

Token counts verified against the English block: {{CANDIDATE_NAME}} ×2, {{POSITION_TITLE}} ×2, all thirteen others ×1. Byte-identical spellings.

2. WHAT I CHANGED from the draft and why

  • "gaji pokok bersih" → "gaji dasar bersih (net)" (all three reviewers, blocker). Gaji pokok/upah pokok is the statutory basic-wage component and is gross by definition — it is the base for THR, lembur, BPJS and pesangon. This repo proves the collision is live: internal/letter/defaults.go prints Gaji pokok : {{salary}} per bulan (bruto) and internal/letter/letter.go documents that field as "Gross monthly". A payroll operator reading "gaji pokok" would deduct PPh 21 and BPJS from {{NET_SALARY}}. Gaji dasar renders "base salary" with no statutory freight. Reviewer 3 wanted to keep "gaji pokok" with a gloss; rejected — the gloss does not disarm a term of art that already means bruto in this same recruitment flow.
  • "berhak mengikuti" → "memenuhi syarat untuk mengikuti" (all three). English says "eligible to participate", not entitled; the draft used the same verb "berhak" for both benefits and vacation, erasing a distinction the English draws deliberately. "Berhak atas cuti" is kept, because there the English does say "entitled".
  • "Kompensasi" → "Kompensasi Tunai" (all three). Without "Tunai" the heading reads as total compensation, letting benefits be argued into the {{NET_SALARY}} figure.
  • "standar" restored twice — "jadwal penggajian standar Perusahaan", "kebijakan internal standar Perusahaan" (R1, R3). English says "standard" in both places; without it the clause points at whatever bespoke arrangement is asserted later.
  • "dapat disesuaikan" → "tunduk pada penyesuaian" (R1). English "will be subject to adjustment" is a standing condition, not a permission.
  • "surat ini" → "perjanjian ini" in Penerimaan (R1, R3). The English self-describes as "this agreement"; calling the signed instrument a mere letter in the governing block invites the argument that the Indonesian version is a notification.
  • "dokumen dan petunjuk selanjutnya" → "yang diperlukan" (R1, R3). "Necessary", not "subsequent".
  • Opening qualifier restored — "atas rencana hubungan kerja Anda bersama kami" (R1, R3), mirroring "your anticipated employment with us", which preserves the offer-not-contract posture.
  • "melapor kepada" → "bertanggung jawab langsung kepada" (R2 only). "Melapor kepada" is a calque that reads as filing a report; this is the conventional Indonesian rendering of a reporting line and changes no promise.
  • "cuti tahunan berbayar" → "cuti tahunan dengan upah penuh" (R2, R3). "Berbayar" in current usage means paid for (cf. "konten berbayar") — the opposite of "paid vacation".
  • Kept "hari kerja" — R1 wanted bare "hari" to mirror the English; R2 and R3 wanted it kept. Kept, because cuti tahunan is counted in hari kerja by statute (UU 13/2003 Pasal 79: minimum 12 hari kerja) and bare "hari" in the governing block invites a calendar-day reading that can fall below the statutory floor. Flagged below.
  • Kept "reguler" in "karyawan reguler Perusahaan" — R2 and R3 preferred deleting "regular" entirely; I restored a neutral rendering. Deleting it makes the Indonesian (the governing block) grant benefit eligibility to every employee where the English conditions it, and "reguler" is not a statutory term, so it carries the English's own vagueness across instead of resolving it one-sidedly. Nobody proposed "karyawan tetap" and it must not be used — on a {{CONTRACT_TYPE}}=Contract letter that is written evidence of PKWTT.
  • Added the "Bahasa." paragraph — R2 and R3 for, R1 against on symmetry grounds. Included: it does not add a term, it restates the default under UU 24/2009 Pasal 31 jo. Perpres 63/2019 and Pasal 57 UU 13/2003, and without it every remaining divergence between the two blocks is unresolvable on the face of a permanently numbered document. The English twin is decision 4 below.
  • Not added: the net-salary definition sentence, the "bukan perjanjian kerja" disclaimer, the gross-basic-wage sentence, the PT Virtue Digital Indonesia party definition, "Perihal"/"Sdr./Sdri."/"di tempat"/"Menyetujui". Each would put a term in the Indonesian block that is absent from the approved English — worse than the ambiguity it fixes. They are decisions, not translation.

3. WHAT EFRAN MUST DECIDE

  1. What is {{NET_SALARY}} net OF? Confirm with payroll: PPh 21 only, or PPh 21 plus the employee-borne BPJS Ketenagakerjaan (JHT 2%, JP 1%) and BPJS Kesehatan (1%). Roughly a 4-5% spread on the transfer amount. Once confirmed, add the same sentence to both blocks at the end of the Kompensasi Tunai / Cash Compensation paragraph — ID: "Yang dimaksud dengan gaji bersih adalah jumlah yang Anda terima setelah dipotong Pajak Penghasilan Pasal 21 dan iuran BPJS Ketenagakerjaan serta BPJS Kesehatan yang menjadi tanggungan karyawan." EN: "Net base salary means the amount you receive after deduction of Article 21 income tax and the employee-borne portion of BPJS Ketenagakerjaan and BPJS Kesehatan contributions." Drop the BPJS half of both sentences if only tax is netted. Also confirm with finance that the company-borne PPh 21 under a net scheme is being handled (non-deductible unless grossed up).
  2. Is {{VACATION_DAYS}} populated in working days or calendar days? The Indonesian says "hari kerja" and the English says neither, so at 20 the blocks differ by about 40%. If working days is intended (it should be — the statutory floor is 12 hari kerja), get one word approved in the English: "paid vacation of {{VACATION_DAYS}} working days per year". Also decide whether the portal should refuse a value below 12.
  3. Are PKWT hires benefit-eligible? The English conditions eligibility on "regular employee" and never defines it. Decide yes or no, then say so in both blocks. Until then the Indonesian says "karyawan reguler", which mirrors the English's vagueness rather than resolving it. Do not let anyone "fix" this with "karyawan tetap".
  4. Approve the English twin of the Bahasa clause. Exact text: "Language. This letter is made in Indonesian and English. In the event of any inconsistency between the two versions, the Indonesian version shall prevail." Placement: its own paragraph in the English block, immediately after the Acceptance paragraph and before "We look forward to welcoming you to the team." — the same slot the Bahasa paragraph occupies in the Indonesian block. If Legal will not reopen the English, the Indonesian paragraph still stays; a one-sided restatement of the statutory default beats silence.
  5. Date format — docs/OFFER_LETTER_DOTX_PLAN.md §7 no. 3 is unexecutable as written. It records "English dates as September 1, 2026" while "the Indonesian one stays for the Indonesian column", but the tokens are shared, so one token carries one string and one block will be wrong. Lock a single format that is correct in both and unambiguous — 1 September 2026, month spelled out, never 01/09/2026 — for {{TANGGAL_SURAT}}, {{START_DATE}} and {{OFFER_EXPIRY_DATE}}. {{OFFER_EXPIRY_DATE}} is a signing deadline; a numeric format is ID/US ambiguous. Correct the plan doc.
  6. One header and one signature block, or two? As drafted the merged page prints {{NOMOR_SURAT}} twice and gives the candidate two signature lines, which reads as two letters in the correspondence register and leaves it open which signature binds. Recommendation: keep the Indonesian header at the top of the page as the sole header, and one signature block at the foot with bilingual labels ("Hormat kami / Sincerely", "Kandidat / Candidate"). Note that doing this drops {{NOMOR_SURAT}}, {{TANGGAL_SURAT}}, {{SIGNATORY_NAME}}, {{SIGNATORY_TITLE}} and one {{CANDIDATE_NAME}} from the English block — that is a .dotx layout change, harmless because docx.ReplaceFields replaces every occurrence, but the per-block token parity rule then applies to the page, not to each block.

Relevant files: /home/efran/remote-development/x056-disc-recruitement/docs/OFFER_LETTER_DOTX_PLAN.md (§7 no. 3, decision table row 3), /home/efran/remote-development/x056-disc-recruitement/internal/letter/defaults.go (the existing "Gaji pokok ... (bruto)" line), /home/efran/remote-development/x056-disc-recruitement/internal/letter/letter.go (salary field documented "Gross monthly", employment_type example "Karyawan tetap").


Resolved — Efran, 2026-08-23

{{NET_SALARY}} is 100% take-home pay

The figure is what lands in the candidate's account. Every deduction — PPh 21 and
the employee-borne BPJS contributions — is carried by the Company, not by the
candidate. So the letter must say so: "net" on its own is exactly the word two
parties read differently, and this document cannot be corrected once numbered.

Add to the end of the Kompensasi Tunai paragraph (Indonesian block):

Yang dimaksud dengan gaji bersih adalah jumlah yang Anda terima setiap bulan.
Pajak Penghasilan Pasal 21 serta iuran BPJS Ketenagakerjaan dan BPJS Kesehatan
yang menjadi tanggungan karyawan ditanggung sepenuhnya oleh Perusahaan.

And to the end of Cash Compensation (English block):

Net base salary means the amount you receive each month. Article 21 income tax
and the employee-borne portion of BPJS Ketenagakerjaan and BPJS Kesehatan
contributions are borne entirely by the Company.

This is the one addition that goes into both blocks. It adds no term the
English did not already imply by saying "net" — it removes an ambiguity about
which side of the deduction the number sits on.

For finance, not for the letter: company-borne PPh 21 is not deductible as a
corporate expense unless it is paid as a tunjangan pajak (grossed up). Worth
confirming which way payroll books it; it does not change a word of the letter.

Date format — correcting the plan

docs/OFFER_LETTER_DOTX_PLAN.md recorded "English dates as September 1, 2026"
with the Indonesian kept separately. That is unexecutable: {{TANGGAL_SURAT}},
{{START_DATE}} and {{OFFER_EXPIRY_DATE}} are single tokens shared by both
blocks, so one string is rendered in both places. One of the two blocks would
always be wrong.

Settled: 1 September 2026 — day first, month spelled out, in both blocks.
It is correct Indonesian and unambiguous English. Never numeric:
{{OFFER_EXPIRY_DATE}} is a signing deadline and 01/09/2026 reads as two
different days on either side of the page.


Built and proven — 2026-08-23

Remaining decisions settled: hari kerja (working days, and the English gained
the word "working" to match), PKWT hires are benefit-eligible (so the
"regular employee" / "karyawan reguler" qualifier is gone from both blocks — it
was the only thing conditioning eligibility, and it conditioned it on a term
nobody had defined), and one header, one signature block with bilingual
labels.

On staging

template  01a02d8d-4fc9-7e7f-9b49-434f73d9610a   "Valid Offer Letter (bilingual)"
proof     VDI/OFR/260007, official copy v2

The .dotx is not kept in this repo. Obscura holds it, a portal admin
re-uploads it, and the portal reads its declared fields at runtime — keeping a
copy here is the exact thing this change removed.

What the filed letter says

Verified by extracting the text of the official copy, not by looking at a
preview: number and date on the page, both blocks rendered, every token
substituted, kop and address from the Word header and footer.

The page number token count differs from the per-block rule the plan first
stated: with one header and one signature block, parity is a property of the
PAGE, not of each block. {{POSITION_TITLE}} appears four times (twice per
block), {{CANDIDATE_NAME}} three (salutation in each block, once in the
signature), {{NOMOR_SURAT}} / {{TANGGAL_SURAT}} / {{SIGNATORY_*}} once
each. docx.ReplaceFields substitutes every occurrence, so this is safe — but
it is why the rule is written down.