61 lines
1.5 KiB
Python
61 lines
1.5 KiB
Python
"""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)
|