Files
OBE-Mapping/app/generator/rtm_docx.py
T
Power BI Dev af369883f3 Tambah halaman RTM (Rancangan Tugas Mahasiswa) tersendiri, terpisah dari Nilai
Dokumen RTM resmi sesuai template baku prodi (FORM RPS & RTM UNTAN), diisi
di halaman /rps/{kode}/rtm sendiri setelah RPS ditandai selesai — blok Tugas
bisa ditambah/dihapus dinamis, dan bisa diunduh sebagai .docx. Murni
dokumentasi, tak menyimpan atau memengaruhi nilai/skor mahasiswa sama sekali.
2026-07-27 07:51:27 +07:00

152 lines
5.5 KiB
Python

# -*- coding: utf-8 -*-
"""
Generator Rancangan Tugas Mahasiswa (RTM) (.docx).
Mengikuti format resmi `FORM RPS & RTM UNTAN (draft).docx`: tabel identitas
(Mata Kuliah/Kode MK/Tugas ke/SKS/Dosen Pengampu/Semester) diikuti tabel
RANCANGAN TUGAS MAHASISWA per tugas (Bentuk, Judul, Sub-CPMK, Deskripsi,
Metode Pengerjaan, Bentuk & Format Luaran, Indikator/Kriteria/Bobot,
Jadwal Pelaksanaan, Lain-lain, Daftar Rujukan). Dokumen ini berdiri sendiri,
tak memuat data nilai/skor mahasiswa sama sekali.
"""
import io
import os
from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Cm, Pt, RGBColor
DARK_BLUE = "1F4E79"
LIGHT_BLUE = "D6E4F0"
WHITE = "FFFFFF"
FAKULTAS = os.environ.get("FAKULTAS", "FAKULTAS TEKNIK")
PRODI = os.environ.get("PRODI", "PROGRAM STUDI INFORMATIKA")
def _shd(cell, hex_color):
tc_pr = cell._tc.get_or_add_tcPr()
el = OxmlElement("w:shd")
el.set(qn("w:val"), "clear")
el.set(qn("w:color"), "auto")
el.set(qn("w:fill"), hex_color)
tc_pr.append(el)
def _ct(cell, text, bold=False, italic=False, size=10, color=None,
align=WD_ALIGN_PARAGRAPH.LEFT):
p = cell.paragraphs[0]
p.alignment = align
p.paragraph_format.space_before = Pt(2)
p.paragraph_format.space_after = Pt(2)
run = p.add_run()
for i, line in enumerate(str(text or "").split("\n")):
if i:
run.add_break()
run.add_text(line)
run.bold = bold
run.italic = italic
run.font.name = "Times New Roman"
run.font.size = Pt(size)
if color:
run.font.color.rgb = RGBColor.from_string(color)
def _hdr(cell, text, size=10):
_shd(cell, DARK_BLUE)
_ct(cell, text, bold=True, color=WHITE, size=size, align=WD_ALIGN_PARAGRAPH.CENTER)
def _borders(table):
tbl_pr = table._tbl.tblPr
el = OxmlElement("w:tblBorders")
for sisi in ("top", "left", "bottom", "right", "insideH", "insideV"):
b = OxmlElement(f"w:{sisi}")
b.set(qn("w:val"), "single")
b.set(qn("w:sz"), "4")
b.set(qn("w:color"), "000000")
el.append(b)
tbl_pr.append(el)
def build_rtm(d: dict) -> bytes:
doc = Document()
for sec in doc.sections:
sec.top_margin = sec.bottom_margin = Cm(2.5)
sec.left_margin = sec.right_margin = Cm(2.5)
def para(text, size=12, bold=False, align=WD_ALIGN_PARAGRAPH.LEFT, before=6, after=3):
p = doc.add_paragraph()
p.alignment = align
p.paragraph_format.space_before = Pt(before)
p.paragraph_format.space_after = Pt(after)
r = p.add_run(text)
r.bold = bold
r.font.name = "Times New Roman"
r.font.size = Pt(size)
return p
para("RANCANGAN TUGAS MAHASISWA (RTM)", size=16, bold=True, align=WD_ALIGN_PARAGRAPH.CENTER)
para(PRODI, size=12, bold=True, align=WD_ALIGN_PARAGRAPH.CENTER, before=0)
para(f"{FAKULTAS} — UNIVERSITAS TANJUNGPURA", size=11, align=WD_ALIGN_PARAGRAPH.CENTER, before=0)
para(f"Tahun Ajaran {d.get('tahun', '')}", size=11, align=WD_ALIGN_PARAGRAPH.CENTER,
before=0, after=10)
daftar_tugas = d.get("tugas", [])
if not daftar_tugas:
para("Belum ada Tugas yang diisi untuk RTM ini.", after=6)
for idx, t in enumerate(daftar_tugas, 1):
if idx > 1:
doc.add_page_break()
ti = doc.add_table(rows=3, cols=4)
_borders(ti)
baris = [
("Mata Kuliah", d.get("nama", ""), "Kode MK", d.get("kode", "")),
("Tugas ke", str(idx), "SKS", str(d.get("sks", "") or "—")),
("Dosen Pengampu", d.get("pengampu", "") or "—", "Semester",
str(d.get("semester", "") or "—")),
]
for ri, (l1, v1, l2, v2) in enumerate(baris):
_shd(ti.cell(ri, 0), LIGHT_BLUE)
_ct(ti.cell(ri, 0), l1, bold=True)
_ct(ti.cell(ri, 1), v1)
_shd(ti.cell(ri, 2), LIGHT_BLUE)
_ct(ti.cell(ri, 2), l2, bold=True)
_ct(ti.cell(ri, 3), v2)
doc.add_paragraph()
subs_teks = ", ".join(t.get("subs") or []) or "—"
jadwal = "; ".join(x for x in (
f"Alokasi waktu: {t['alokasi_waktu']}" if t.get("alokasi_waktu") else "",
f"Tanggal: {t['tanggal']}" if t.get("tanggal") else "",
) if x) or "—"
rows = [
("BENTUK TUGAS", t.get("bentuk_tugas", "") or "—"),
("JUDUL TUGAS", t.get("judul", "") or "—"),
("SUB-CPMK YANG DICAPAI", subs_teks),
("DESKRIPSI TUGAS", t.get("deskripsi", "") or "—"),
("METODE PENGERJAAN TUGAS", t.get("metode_pengerjaan", "") or "—"),
("BENTUK DAN FORMAT LUARAN", t.get("luaran", "") or "—"),
("INDIKATOR, KRITERIA DAN BOBOT PENILAIAN", t.get("indikator_bobot", "") or "—"),
("JADWAL PELAKSANAAN", jadwal),
("LAIN-LAIN", t.get("lain_lain", "") or "—"),
("DAFTAR RUJUKAN", t.get("rujukan", "") or "—"),
]
tt = doc.add_table(rows=1 + len(rows), cols=2)
_borders(tt)
_hdr(tt.cell(0, 0), "RANCANGAN TUGAS MAHASISWA")
tt.cell(0, 0).merge(tt.cell(0, 1))
for ri, (label, isi) in enumerate(rows, 1):
_shd(tt.cell(ri, 0), LIGHT_BLUE)
_ct(tt.cell(ri, 0), label, bold=True, size=9)
_ct(tt.cell(ri, 1), isi, size=10)
tt.rows[ri].cells[0].width = Cm(5.5)
tt.rows[ri].cells[1].width = Cm(11)
buf = io.BytesIO()
doc.save(buf)
return buf.getvalue()