from zipfile import ZipFile, ZIP_DEFLATED from xml.sax.saxutils import escape from pathlib import Path OUT = Path(__file__).with_name("akses-tabel-cpone-middleware.docx") W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main" R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships" CT = "http://schemas.openxmlformats.org/package/2006/content-types" PR = "http://schemas.openxmlformats.org/package/2006/relationships" def qn(tag): prefix, local = tag.split(":", 1) return f"{{{ {'w': W, 'r': R, 'ct': CT, 'pr': PR}[prefix] }}}{local}" def attr(name, value): return f' {name}="{escape(str(value))}"' def run(text, bold=False, color=None, size=21, font="Calibri", italic=False): rpr = [f'', f'', f''] if bold: rpr.append('') if italic: rpr.append('') if color: rpr.append(f'') return f'{"".join(rpr)}{escape(text)}' def paragraph(text="", style="Normal", before=0, after=120, color=None, bold=False, size=21, align=None, keep=False, page_break=False): ppr = [f'', f''] if align: ppr.append(f'') if keep: ppr.append('') if page_break: ppr.append('') body = run(text, bold=bold, color=color, size=size) if text else '' return f'{"".join(ppr)}{body}' def code_paragraph(text, before=80, after=160): ppr = [ '', f'', ] content = [] for index, line in enumerate(text.splitlines()): if index: content.append('') content.append(run(line, size=16, font="Consolas")) return f'{"".join(ppr)}{"".join(content)}' def rich_paragraph(parts, style="Normal", before=0, after=120, align=None): ppr = [f'', f''] if align: ppr.append(f'') content = ''.join(run(text, bold=bold, color=color, size=size) for text, bold, color, size in parts) return f'{"".join(ppr)}{content}' def cell(text, width, header=False, align="left"): fill = 'E8EEF5' if header else 'FFFFFF' color = '0B2545' if header else '1F2937' size = 18 if header else 18 tcpr = f'' ppr = f'' text_xml = run(text, bold=header, color=color, size=size, font="Consolas" if (not header and ("_" in text or "{" in text or "}" in text)) else "Calibri") return f'{tcpr}{ppr}{text_xml}' def table(headers, rows, widths, aligns=None): aligns = aligns or ["left"] * len(widths) grid = ''.join(f'' for w in widths) borders = '' borders = borders.replace("AEB DCD", "AEBDCD") cellmar = '' tblpr = f'{borders}{cellmar}' head = '' + ''.join(cell(h, w, True, aligns[i]) for i, (h, w) in enumerate(zip(headers, widths))) + '' body = ''.join('' + ''.join(cell(str(value), w, False, aligns[i]) for i, (value, w) in enumerate(zip(row, widths))) + '' for row in rows) return f'{tblpr}{grid}{head}{body}' def bullet(text): return '' + run(text, size=20) + '' body = [] body.append(paragraph("CPONE MIDDLEWARE • DATABASE ACCESS REFERENCE", color="2E74B5", bold=True, size=16, after=100)) body.append(paragraph("Daftar Akses Tabel", style="DocTitle", size=48, color="0B2545", after=80)) body.append(paragraph("Kebutuhan privilege database untuk service CPONE Middleware", color="475569", size=24, after=220)) body.append(table(["Informasi", "Nilai"], [ ("Database target", "Database HIS pada CPONE_DB_DATABASE"), ("Database user", "User pada CPONE_DB_USERNAME"), ("Dasar pemetaan", "Query SQL pada folder internal/repository"), ("Tanggal", "17 September 2026"), ], [1900, 7460], ["left", "left"])) body.append(rich_paragraph([("Ringkasan: ", True, "0B2545", 20), ("service membutuhkan SELECT pada tabel master dan tabel transaksi yang dibaca. Akses write hanya diperlukan pada tabel yang digunakan endpoint POST untuk membuat paket, pasien, registrasi, layanan laboratorium, nominal, dan diagnosis.", False, "1F2937", 20)], after=180)) body.append(paragraph("1. Tabel dengan READ dan WRITE", style="Heading1", before=260, after=100, keep=True)) body.append(paragraph("Tabel berikut membutuhkan SELECT dan operasi write seperti tercantum.", after=100)) body.append(table(["Nama tabel", "READ", "WRITE", "Kegunaan utama"], [ ("medrec", "SELECT", "INSERT", "Pencarian dan pembuatan data pasien/medical record."), ("regpas", "SELECT", "INSERT", "Pembuatan dan pengecekan registrasi pasien."), ("regpas_nominal", "SELECT", "INSERT, UPDATE", "Nominal administrasi pada level registrasi pasien."), ("trxdepartemen", "SELECT", "INSERT", "Pembuatan transaksi departemen."), ("trxdepartemen_nominal", "SELECT", "INSERT, UPDATE", "Rekap nominal layanan/item/tempat tidur dan total transaksi."), ("trxidlaboratorium", "SELECT", "INSERT", "Pembuatan nomor/transaksi laboratorium."), ("trxlayanan", "SELECT", "INSERT", "Penambahan layanan pada transaksi."), ("trxlayanan_detail", "SELECT", "INSERT", "Relasi detail layanan dengan paket dispenser."), ("trxlayanan_penunjang", "SELECT", "INSERT", "Relasi layanan penunjang/header laboratorium."), ("lab_trxspesimen", "SELECT", "INSERT", "Pencatatan spesimen laboratorium."), ("paket_header", "SELECT", "INSERT, UPDATE, DELETE", "Pembuatan paket; DELETE hanya untuk cleanup saat transaksi gagal."), ("paket_layanan", "SELECT", "INSERT, DELETE", "Detail layanan paket; DELETE hanya untuk cleanup."), ("paket_departemen", "SELECT", "INSERT, DELETE", "Departemen terkait paket; DELETE hanya untuk cleanup."), ("paket_dispenser_header", "SELECT", "INSERT", "Header paket dispenser pada kunjungan pasien."), ("paket_dispenser_episode_rj", "SELECT", "INSERT", "Episode kunjungan rawat jalan paket dispenser."), ("paket_dispenser_layanan", "SELECT", "INSERT", "Layanan yang dihasilkan dari paket dispenser."), ("paket_dispenser_item", "SELECT", "INSERT", "Item paket dispenser."), ("paket_dispenser_adm", "SELECT", "INSERT", "Administrasi paket dispenser."), ], [2600, 1000, 1600, 4160], ["left", "center", "center", "left"])) body.append(paragraph("2. Tabel dengan WRITE saja", style="Heading1", before=260, after=100, keep=True)) body.append(table(["Nama tabel", "Operasi dan kegunaan"], [ ("diagnosa_log", "INSERT — log diagnosis kerja pada registrasi laboratorium."), ("regpas_diagnosa", "INSERT — diagnosis kerja yang terkait dengan pasien/registrasi."), ], [2600, 6760], ["left", "left"])) body.append(paragraph("Kedua tabel ini tidak dibaca kembali oleh service dalam query saat ini, tetapi harus dapat menerima INSERT ketika endpoint registrasi laboratorium menyimpan diagnosis.", color="475569", size=18, after=100)) body.append(paragraph("3. Tabel dengan READ saja", style="Heading1", before=260, after=100, keep=True)) body.append(paragraph("Tabel master dan referensi berikut hanya dibaca oleh service, sehingga cukup diberikan privilege SELECT.", after=100)) body.append(table(["Kelompok", "Nama tabel"], [ ("Tarif dan layanan", "adm_periode, harga_layanan_diskon, layanandepartemen, masterlayanan, jenislayanan, periode_tarif, perusahaan_grouptarif, perusahaan_tarif, perusahaan"), ("Master rumah sakit", "departemen, dokter, rs, kelas, satuan, satuanlab, shiftharian"), ("Master paket", "paket_adm, paket_item, paket_operasi_header, paket_tt, paket_dispenser_operasi_header"), ("Master operasi dan referensi", "golonganoperasi, jenisanestesi, kasusoperasi, mastertindakan, konversisatuan, margin_alkes, margin_obat, sys_setting"), ("Master laboratorium", "grouplayanan, lab_jenisspesimen, lab_masterlayanan, lab_multiple, lab_reference, lab_spesimentest, lab_trxlayanan_setup, trxlayananlaboratorium, trxlayanan_proses"), ("Master pasien/registrasi", "jenispaket, jenispasien, jenisreg"), ("Metadata MySQL", "information_schema.tables, information_schema.columns"), ], [2500, 6860], ["left", "left"])) body.append(paragraph("4. Tabel dinamis", style="Heading1", before=260, after=100, keep=True)) body.append(table(["Pola nama tabel", "Kebutuhan akses"], [ ("harga_layanan_{PeriodeTarifID}", "SELECT. Contoh: harga_layanan_10. Dipakai untuk membaca tarif layanan berdasarkan periode tarif."), ("perusahaan_tarif_layanan_{GroupTarifID}", "SELECT. Dipakai untuk membaca detail tarif kerja sama berdasarkan group tarif."), ], [3000, 6360], ["left", "left"])) body.append(paragraph("5. Rekomendasi grant", style="Heading1", before=260, after=100, keep=True)) body.append(paragraph("Berikan privilege secara eksplisit pada database HIS. Ganti nama database, user, dan host sesuai environment server.", after=100)) grant_text = """-- Ganti nama_database_his, cpone, dan host_user sesuai environment. GRANT SELECT ON `nama_database_his`.`adm_periode` TO 'cpone'@'host_user'; -- Contoh tabel yang membutuhkan write: GRANT SELECT, INSERT ON `nama_database_his`.`medrec` TO 'cpone'@'host_user'; GRANT SELECT, INSERT ON `nama_database_his`.`regpas` TO 'cpone'@'host_user'; GRANT SELECT, INSERT, UPDATE ON `nama_database_his`.`regpas_nominal` TO 'cpone'@'host_user'; GRANT SELECT, INSERT, UPDATE ON `nama_database_his`.`trxdepartemen_nominal` TO 'cpone'@'host_user'; GRANT SELECT, INSERT, UPDATE, DELETE ON `nama_database_his`.`paket_header` TO 'cpone'@'host_user'; -- Ulangi pola di atas untuk seluruh tabel pada daftar ini. -- Tabel dinamis memerlukan SELECT pada setiap tabel yang benar-benar digunakan.""" body.append(code_paragraph(grant_text)) body.append(paragraph("6. Catatan penting", style="Heading1", before=260, after=100, keep=True)) for item in [ "Service hanya membuka koneksi ke database HIS melalui konfigurasi CPONE_DB_*.", "Privilege DELETE pada paket_header, paket_layanan, dan paket_departemen diperlukan untuk cleanup jika proses pembuatan paket gagal sebagian.", "regpas_nominal dibuat saat registrasi laboratorium untuk inisialisasi nominal administrasi dengan nilai awal nol dan metode Otomatis.", "Tabel hospitals dan hospital_databases pada migration cpone_middleware belum digunakan oleh runtime Go service saat ini.", "Daftar ini dipetakan dari kode aplikasi; validasi final tetap perlu disesuaikan dengan struktur tabel dan policy keamanan database HIS.", ]: body.append(bullet(item)) body.append(paragraph("Dokumen internal • Sumber pemetaan: repository CPONE Middleware • Versi berdasarkan kode yang tersedia pada 17 September 2026", color="64748B", size=16, before=240, after=0)) document = f''' {''.join(body)}''' styles = f''' ''' numbering = f''' ''' document_rels = f''' ''' footer = f'''{run("CPONE Middleware • ", color="64748B", size=16)}1''' header = f'''{run("CPONE Middleware | Daftar Akses Tabel", color="64748B", size=16)}''' content_types = f'''''' root_rels = f'''''' core = f'''Daftar Akses Tabel CPONE MiddlewareCPONE MiddlewareDatabase access reference''' app = f'''CPONE Middleware''' with ZipFile(OUT, "w", ZIP_DEFLATED) as z: z.writestr("[Content_Types].xml", content_types) z.writestr("_rels/.rels", root_rels) z.writestr("word/document.xml", document) z.writestr("word/styles.xml", styles) z.writestr("word/numbering.xml", numbering) z.writestr("word/header1.xml", header) z.writestr("word/footer1.xml", footer) z.writestr("word/_rels/document.xml.rels", document_rels) z.writestr("docProps/core.xml", core) z.writestr("docProps/app.xml", app) print(OUT)