"""Modul prosedural validasi checkout (FR-ORD-02, FR-ORD-03, FR-ORD-08). Harga diambil dari katalog, bukan dari input klien. Satu order hanya boleh berisi produk dari satu toko. Keranjang kosong mengembalikan total 0; penolakan keranjang kosong menjadi tanggung jawab pemanggil. """ from dataclasses import dataclass class CheckoutError(Exception): """Kelas dasar semua kegagalan validasi checkout.""" class ProductNotFoundError(CheckoutError): pass class InvalidQuantityError(CheckoutError): pass class MultiShopError(CheckoutError): pass class InsufficientStockError(CheckoutError): pass @dataclass(frozen=True) class CartItem: product_id: str quantity: int @dataclass(frozen=True) class CheckoutSummary: shop_id: str | None total_amount: int def validate_checkout(items, products): """Validasi keranjang terhadap katalog lalu hitung total dalam rupiah.""" shop_id = None total = 0 for item in items: product = products.get(item.product_id) if product is None: raise ProductNotFoundError(item.product_id) if item.quantity <= 0: raise InvalidQuantityError(item.product_id) if shop_id is None: shop_id = product.shop_id elif product.shop_id != shop_id: raise MultiShopError(item.product_id) if product.available < item.quantity: raise InsufficientStockError(item.product_id) total += product.price * item.quantity return CheckoutSummary(shop_id, total)