"""Mesin status order (FR-ORD-04, FR-ORD-09, FR-ORD-10, UC-07, UC-08).""" from datetime import timedelta from enum import Enum AUTO_COMPLETE_AFTER = timedelta(days=3) class OrderStatus(Enum): PENDING_PAYMENT = "PENDING_PAYMENT" PAID = "PAID" SHIPPED = "SHIPPED" COMPLETED = "COMPLETED" CANCELLED = "CANCELLED" EXPIRED = "EXPIRED" class InvalidTransitionError(Exception): """Event tidak diizinkan pada status order saat ini.""" class PermissionDeniedError(Exception): """Pelaku tidak berhak memicu event ini.""" S = OrderStatus # Tabel transisi: (status asal, event) -> status tujuan. Pasangan lain ditolak. TRANSITIONS = { (S.PENDING_PAYMENT, "pay"): S.PAID, (S.PENDING_PAYMENT, "cancel"): S.CANCELLED, (S.PENDING_PAYMENT, "expire"): S.EXPIRED, (S.PAID, "ship"): S.SHIPPED, (S.SHIPPED, "complete"): S.COMPLETED, } class Order: def __init__(self, order_id, buyer_id, seller_id): self.order_id = order_id self.buyer_id = buyer_id self.seller_id = seller_id self.status = S.PENDING_PAYMENT self.shipped_at = None def _fire(self, event): target = TRANSITIONS.get((self.status, event)) if target is None: raise InvalidTransitionError(f"{event} tidak valid pada status {self.status.value}") self.status = target def pay(self): """Event payment.succeeded dari Payment Service.""" self._fire("pay") def cancel(self, actor_id): """Pembeli membatalkan order yang belum dibayar.""" if actor_id != self.buyer_id: raise PermissionDeniedError("hanya pembeli yang dapat membatalkan") self._fire("cancel") def expire(self): """Batas waktu pembayaran habis (dipicu sistem).""" self._fire("expire") def ship(self, actor_id, now): """Seller pemilik toko menandai order terkirim.""" if actor_id != self.seller_id: raise PermissionDeniedError("hanya seller pemilik toko yang dapat mengirim") self._fire("ship") self.shipped_at = now def confirm_received(self, actor_id): """Pembeli mengonfirmasi pesanan diterima. Seller tidak boleh.""" if actor_id != self.buyer_id: raise PermissionDeniedError("hanya pembeli yang dapat mengonfirmasi") self._fire("complete") def auto_complete(self, now): """Sistem menyelesaikan order 3 hari setelah SHIPPED tanpa konfirmasi.""" if self.status is S.SHIPPED and now - self.shipped_at < AUTO_COMPLETE_AFTER: raise InvalidTransitionError("belum 3 hari sejak SHIPPED") self._fire("complete")