saindras

pbo-pertemuan-11-perpustakaan_backend.py

Nov 11th, 2025 (edited)
112
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 14.11 KB | None | 0 0
  1. from abc import ABC, abstractmethod
  2. from datetime import datetime, timedelta
  3. from enum import Enum
  4. import uuid
  5. import pickle
  6. import os
  7.  
  8. # ==============================================================================
  9. # Definisi Enum untuk Status
  10. # ==============================================================================
  11.  
  12. class StatusItem(Enum):
  13.     """Enum untuk status item di perpustakaan."""
  14.     TERSEDIA = "Tersedia"
  15.     DIPINJAM = "Dipinjam"
  16.     HILANG = "Hilang"
  17.  
  18. class StatusTransaksi(Enum):
  19.     """Enum untuk status transaksi peminjaman."""
  20.     AKTIF = "Aktif"
  21.     SELESAI = "Selesai"
  22.     TERLAMBAT = "Terlambat"
  23.  
  24. # ==============================================================================
  25. # Hierarki Kelas Item Pustaka (LoanableItem, Buku, Majalah)
  26. # ==============================================================================
  27.  
  28. class LoanableItem(ABC):
  29.     """
  30.    Kelas abstrak untuk semua item yang dapat dipinjam di perpustakaan.
  31.    Ini adalah superclass (induk) untuk Buku, Majalah, dll.
  32.    """
  33.     def __init__(self, item_id: str, judul: str):
  34.         self._item_id = item_id
  35.         self._judul = judul
  36.         self._status = StatusItem.TERSEDIA
  37.  
  38.     @abstractmethod
  39.     def get_info(self) -> dict:
  40.         """Metode abstrak untuk mendapatkan informasi detail dari item."""
  41.         pass
  42.  
  43.     def ubah_status(self, status_baru: StatusItem):
  44.         """Mengubah status ketersediaan item."""
  45.         self._status = status_baru
  46.         print(f"INFO: Status '{self._judul}' telah diubah menjadi {status_baru.value}.")
  47.  
  48.     @property
  49.     def item_id(self):
  50.         return self._item_id
  51.  
  52.     @property
  53.     def judul(self):
  54.         return self._judul
  55.  
  56.     @property
  57.     def status(self):
  58.         return self._status
  59.  
  60. class Buku(LoanableItem):
  61.     """Kelas turunan untuk merepresentasikan sebuah buku."""
  62.     def __init__(self, item_id: str, judul: str, pengarang: str):
  63.         super().__init__(item_id, judul)
  64.         self._pengarang = pengarang
  65.  
  66.     def get_info(self) -> dict:
  67.         """Implementasi spesifik untuk menampilkan info buku (method overriding)."""
  68.         return {
  69.             "ID": self._item_id,
  70.             "Judul": self._judul,
  71.             "Tipe": "Buku",
  72.             "Pengarang": self._pengarang,
  73.             "Status": self._status.value
  74.         }
  75.  
  76. class Majalah(LoanableItem):
  77.     """Kelas turunan untuk merepresentasikan sebuah majalah/jurnal."""
  78.     def __init__(self, item_id: str, judul: str, edisi: str):
  79.         super().__init__(item_id, judul)
  80.         self._edisi = edisi
  81.  
  82.     def get_info(self) -> dict:
  83.         """Implementasi spesifik untuk menampilkan info majalah (method overriding)."""
  84.         return {
  85.             "ID": self._item_id,
  86.             "Judul": self._judul,
  87.             "Tipe": "Majalah",
  88.             "Edisi": self._edisi,
  89.             "Status": self._status.value
  90.         }
  91.  
  92. # ==============================================================================
  93. # Kelas-kelas Utama Sistem
  94. # ==============================================================================
  95.  
  96. class Member:
  97.     """Merepresentasikan anggota perpustakaan."""
  98.     def __init__(self, nama: str, alamat: str):
  99.         self._member_id = "MEM-" + str(uuid.uuid4())[:8].upper()
  100.         self._nama = nama
  101.         self._alamat = alamat
  102.  
  103.     @property
  104.     def member_id(self):
  105.         return self._member_id
  106.    
  107.     @property
  108.     def nama(self):
  109.         return self._nama
  110.  
  111.     def get_riwayat_peminjaman(self, perpus: 'Perpustakaan') -> list['Transaksi']:
  112.         """Mendapatkan daftar transaksi yang pernah dilakukan oleh member."""
  113.         riwayat = []
  114.         for transaksi in perpus.daftar_transaksi:
  115.             if transaksi.member.member_id == self._member_id:
  116.                 riwayat.append(transaksi)
  117.         return riwayat
  118.  
  119. class Transaksi:
  120.     """Merepresentasikan sebuah transaksi peminjaman."""
  121.     def __init__(self, member: Member, item: LoanableItem):
  122.         self._transaksi_id = "TRX-" + str(uuid.uuid4())[:8].upper()
  123.         self._member = member
  124.         self._item = item
  125.         self._tgl_pinjam = datetime.now()
  126.         self._tgl_kembali = self._tgl_pinjam + timedelta(days=14) # Batas peminjaman 14 hari
  127.         self._status = StatusTransaksi.AKTIF
  128.         self._denda = 0.0
  129.  
  130.     @property
  131.     def transaksi_id(self):
  132.         return self._transaksi_id
  133.  
  134.     @property
  135.     def member(self):
  136.         return self._member
  137.        
  138.     @property
  139.     def item(self):
  140.         return self._item
  141.  
  142.     @property
  143.     def status(self):
  144.         return self._status
  145.  
  146.     def hitung_denda(self) -> float:
  147.         """Menghitung denda jika pengembalian terlambat."""
  148.         if datetime.now() > self._tgl_kembali:
  149.             terlambat = (datetime.now() - self._tgl_kembali).days
  150.             self._denda = float(terlambat * 1000) # Denda 1000 per hari
  151.             self._status = StatusTransaksi.TERLAMBAT
  152.         return self._denda
  153.        
  154.     def selesaikan_transaksi(self):
  155.         self.hitung_denda()
  156.         if self._status != StatusTransaksi.TERLAMBAT:
  157.             self._status = StatusTransaksi.SELESAI
  158.  
  159. class Perpustakaan:
  160.     """Kelas utama yang mengelola seluruh sistem perpustakaan."""
  161.    
  162.     # Kode sebelumnya:
  163.  
  164.     # def __init__(self):
  165.     #    self._daftar_item = []
  166.     #    self._daftar_member = []
  167.     #    self._daftar_transaksi = []
  168.     #    print("Sistem Perpustakaan berhasil dibuat.")
  169.  
  170.     def __init__(self, nama_file_data="perpustakaan.pkl"):
  171.         """
  172.        Inisialisasi perpustakaan.
  173.        Akan mencoba memuat data dari file jika ada.
  174.        """
  175.         self._nama_file_data = nama_file_data
  176.  
  177.         # Coba muat data dulu
  178.         if not self.muat_data():
  179.             # Jika gagal (file tidak ada), buat list kosong
  180.             print("INFO: File data tidak ditemukan. Membuat database baru...")
  181.             self._daftar_item = []
  182.             self._daftar_member = []
  183.             self._daftar_transaksi = []
  184.        
  185.         print("Sistem Perpustakaan berhasil dibuat.")
  186.  
  187.     @property
  188.     def daftar_item(self):
  189.         return self._daftar_item
  190.        
  191.     @property
  192.     def daftar_member(self):
  193.         return self._daftar_member
  194.        
  195.     @property
  196.     def daftar_transaksi(self):
  197.         return self._daftar_transaksi
  198.  
  199.     def muat_data(self) -> bool:
  200.         """
  201.        Memuat state perpustakaan (daftar item, member, transaksi)
  202.        dari file pickle.
  203.        Mengembalikan True jika berhasil, False jika gagal.
  204.        """
  205.         if not os.path.exists(self._nama_file_data):
  206.             return False # File tidak ada, gagal memuat
  207.        
  208.         try:
  209.             # Buka file dalam mode Read Binary ('rb')
  210.             with open(self._nama_file_data, 'rb') as f:
  211.                 # Muat data yang disimpan
  212.                 data_dimuat = pickle.load(f)
  213.                
  214.                 # Kembalikan state objek dari dictionary
  215.                 self._daftar_item = data_dimuat.get("items", [])
  216.                 self._daftar_member = data_dimuat.get("members", [])
  217.                 self._daftar_transaksi = data_dimuat.get("transaksi", [])
  218.                
  219.                 print(f"INFO: Data perpustakaan berhasil dimuat dari {self._nama_file_data}.")
  220.                 return True
  221.         except Exception as e:
  222.             print(f"ERROR saat memuat data: {e}")
  223.             return False
  224.    
  225.     def simpan_data(self):
  226.         """
  227.        Menyimpan state perpustakaan (daftar item, member, transaksi)
  228.        ke dalam file pickle.
  229.        """
  230.         # Siapkan data yang akan disimpan dalam format dictionary
  231.         data_untuk_disimpan = {
  232.             "items": self._daftar_item,
  233.             "members": self._daftar_member,
  234.             "transaksi": self._daftar_transaksi
  235.         }
  236.        
  237.         try:
  238.             # Buka file dalam mode Write Binary ('wb')
  239.             with open(self._nama_file_data, 'wb') as f:
  240.                 # Simpan data menggunakan pickle
  241.                 pickle.dump(data_untuk_disimpan, f)
  242.             print(f"\nINFO: Data perpustakaan berhasil disimpan ke {self._nama_file_data}.")
  243.         except Exception as e:
  244.             print(f"ERROR saat menyimpan data: {e}")
  245.    
  246.     def tambah_item(self, item: LoanableItem):
  247.         """Menambahkan item baru (buku/majalah) ke perpustakaan."""
  248.         self._daftar_item.append(item)
  249.         print(f"ITEM DITAMBAHKAN: '{item.judul}' ({item.__class__.__name__}) telah ditambahkan ke koleksi.")
  250.  
  251.     def tambah_member(self, nama: str, alamat: str) -> Member:
  252.         """Mendaftarkan anggota baru."""
  253.         member_baru = Member(nama, alamat)
  254.         self._daftar_member.append(member_baru)
  255.         print(f"MEMBER BARU: {nama} dengan ID {member_baru.member_id} berhasil terdaftar.")
  256.         return member_baru
  257.  
  258.     def cari_item(self, item_id: str) -> LoanableItem | None:
  259.         """Mencari item berdasarkan ID."""
  260.         for item in self._daftar_item:
  261.             if item.item_id == item_id:
  262.                 return item
  263.         return None
  264.  
  265.     def cari_member(self, member_id: str) -> Member | None:
  266.         """Mencari member berdasarkan ID."""
  267.         for member in self._daftar_member:
  268.             if member.member_id == member_id:
  269.                 return member
  270.         return None
  271.        
  272.     def cari_transaksi(self, transaksi_id: str) -> Transaksi | None:
  273.         """Mencari transaksi berdasarkan ID."""
  274.         for trx in self._daftar_transaksi:
  275.             if trx.transaksi_id == transaksi_id:
  276.                 return trx
  277.         return None
  278.  
  279.     def pinjamkan_item(self, member_id: str, item_id: str) -> Transaksi:
  280.         """Memproses peminjaman item oleh member."""
  281.         member = self.cari_member(member_id)
  282.         item = self.cari_item(item_id)
  283.  
  284.         if not member:
  285.             raise Exception("ERROR: Member tidak ditemukan.")
  286.         if not item:
  287.             raise Exception("ERROR: Item tidak ditemukan.")
  288.         if item.status != StatusItem.TERSEDIA:
  289.             raise Exception(f"ERROR: Item '{item.judul}' sedang tidak tersedia.")
  290.  
  291.         transaksi_baru = Transaksi(member, item)
  292.         self._daftar_transaksi.append(transaksi_baru)
  293.         item.ubah_status(StatusItem.DIPINJAM)
  294.         print(f"TRANSAKSI PEMINJAMAN: '{item.judul}' dipinjam oleh {member.nama}. ID Transaksi: {transaksi_baru.transaksi_id}")
  295.         return transaksi_baru
  296.  
  297.     def terima_pengembalian(self, transaksi_id: str):
  298.         """Memproses pengembalian item."""
  299.         transaksi = self.cari_transaksi(transaksi_id)
  300.         if not transaksi:
  301.             raise Exception("ERROR: Transaksi tidak ditemukan.")
  302.         if transaksi.status != StatusTransaksi.AKTIF:
  303.             raise Exception("ERROR: Transaksi ini sudah tidak aktif.")
  304.        
  305.         transaksi.selesaikan_transaksi()
  306.         item = transaksi.item
  307.         item.ubah_status(StatusItem.TERSEDIA)
  308.        
  309.         denda = transaksi.hitung_denda()
  310.         print(f"TRANSAKSI PENGEMBALIAN: '{item.judul}' telah dikembalikan oleh {transaksi.member.nama}.")
  311.         if denda > 0:
  312.             print(f"   -> Dikenakan denda sebesar: Rp{denda:,.2f}")
  313.         else:
  314.             print("   -> Pengembalian tepat waktu.")
  315.  
  316. # Modifikasi Kelas Pustakawan
  317. class Pustakawan:
  318.     """Merepresentasikan pustakawan yang mengelola perpustakaan."""
  319.     def __init__(self, pustakawan_id: str, nama: str):
  320.         self._pustakawan_id = pustakawan_id
  321.         self._nama = nama
  322.  
  323.     def lakukan_pendaftaran(self, perpus: Perpustakaan, nama: str, alamat: str) -> Member:
  324.         """Wrapper method untuk mendaftarkan member."""
  325.         print(f"\n[Aksi Pustakawan: {self._nama}] Mendaftarkan anggota baru...")
  326.         return perpus.tambah_member(nama, alamat)
  327.  
  328.     # Versi Baru dengan Factory
  329.     def tambah_item_ke_koleksi(self, perpus: Perpustakaan, item_type: str, item_id: str, judul: str, **kwargs):
  330.         """Wrapper method untuk menambah item baru menggunakan Factory."""
  331.         print(f"\n[Aksi Pustakawan: {self._nama}] Meminta pembuatan dan penambahan item '{judul}'...")
  332.         try:
  333.             # Panggil Factory untuk membuat objek
  334.             item_baru = ItemFactory.create_item(item_type, item_id, judul, **kwargs)
  335.             # Tambahkan objek yang sudah jadi ke perpustakaan
  336.             perpus.tambah_item(item_baru)
  337.         except ValueError as e:
  338.             print(f"ERROR saat menambah item: {e}")
  339.         except Exception as e:
  340.             print(f"ERROR tidak terduga: {e}")
  341.  
  342.     def lakukan_peminjaman(self, perpus: Perpustakaan, member_id: str, item_id: str) -> Transaksi:
  343.         """Wrapper method untuk melakukan peminjaman."""
  344.         print(f"\n[Aksi Pustakawan: {self._nama}] Memproses peminjaman...")
  345.         return perpus.pinjamkan_item(member_id, item_id)
  346.        
  347.     def lakukan_pengembalian(self, perpus: Perpustakaan, transaksi_id: str):
  348.         """Wrapper method untuk melakukan pengembalian."""
  349.         print(f"\n[Aksi Pustakawan: {self._nama}] Memproses pengembalian...")
  350.         perpus.terima_pengembalian(transaksi_id)
  351.  
  352. # Kelas Baru: ItemFactory
  353. class ItemFactory:
  354.     """Kelas Factory untuk membuat instance LoanableItem."""
  355.  
  356.     @staticmethod
  357.     def create_item(item_type: str, item_id: str, judul: str, **kwargs) -> LoanableItem:
  358.         """
  359.        Membuat objek Buku atau Majalah berdasarkan item_type.
  360.        kwargs bisa berisi 'pengarang' untuk Buku atau 'edisi' untuk Majalah.
  361.        """
  362.         if item_type.lower() == "buku":
  363.             pengarang = kwargs.get('pengarang', 'N/A') # Ambil pengarang, default N/A
  364.             if pengarang == 'N/A':
  365.                  print("WARNING: Pengarang tidak disediakan untuk buku.")
  366.             return Buku(item_id, judul, pengarang)
  367.         elif item_type.lower() == "majalah":
  368.             edisi = kwargs.get('edisi', 'N/A') # Ambil edisi, default N/A
  369.             if edisi == 'N/A':
  370.                  print("WARNING: Edisi tidak disediakan untuk majalah.")
  371.             return Majalah(item_id, judul, edisi)
  372.         # Bisa tambahkan tipe lain di sini (misal: Artikel)
  373.         # elif item_type.lower() == "artikel":
  374.         #     volume = kwargs.get('volume', 0)
  375.         #     return Artikel(item_id, judul, volume)
  376.         else:
  377.             raise ValueError(f"Tipe item tidak dikenal: {item_type}")
Advertisement
Add Comment
Please, Sign In to add comment