"""Model produk Catalog. Atribut khusus per kategori mengikuti AR-01 / FR-CAT-03.""" from abc import ABC, abstractmethod from datetime import date CLOTHING_SIZES = ("S", "M", "L", "XL") class Product(ABC): """Kelas dasar produk. Subclass menentukan atribut khusus kategorinya.""" category = "generic" def __init__(self, product_id, shop_id, name, price, on_hand, reserved=0): if price < 0: raise ValueError("price tidak boleh negatif") if not 0 <= reserved <= on_hand: raise ValueError("reserved harus di antara 0 dan on_hand") self.product_id = product_id self.shop_id = shop_id self.name = name self.price = price self.on_hand = on_hand self.reserved = reserved @property def available(self): """Stok yang dapat dipesan: onHand - reserved (glosarium SRS).""" return self.on_hand - self.reserved def is_orderable(self, today): """Aturan dasar: produk dapat dipesan jika masih ada stok tersedia.""" return self.available > 0 @abstractmethod def attributes(self): """Atribut khusus kategori, dikembalikan sebagai dict.""" class FoodProduct(Product): category = "food" def __init__(self, product_id, shop_id, name, price, on_hand, expiry_date, reserved=0): super().__init__(product_id, shop_id, name, price, on_hand, reserved) self.expiry_date = expiry_date def is_orderable(self, today): """Makanan tidak boleh dipesan setelah tanggal kedaluwarsa.""" return super().is_orderable(today) and today <= self.expiry_date def attributes(self): return {"expiryDate": self.expiry_date.isoformat()} class ClothingProduct(Product): category = "clothing" def __init__(self, product_id, shop_id, name, price, on_hand, size, reserved=0): if size not in CLOTHING_SIZES: raise ValueError(f"size harus salah satu dari {CLOTHING_SIZES}") super().__init__(product_id, shop_id, name, price, on_hand, reserved) self.size = size def attributes(self): return {"size": self.size}