dodil/erp-brick-pos
DODIL ERP brick: point of sale
src/brick_pos/domain/deposit.py
"""The CashDeposit: a sealed bag of cash on its way to the bank (POS-19). Keyed dep:pos:<shop_id>:<bag_no>: a bag is sealed once and
never unsealed; its amount is positive, inside NUMERIC(18,2) and at most what the shop's safe holds. payments takes it as an
expected deposit batch (POS-26) from a pending outbox row after the seal commits, then matches the bank's credit to it."""
from dataclasses import dataclass, field
from datetime import date, datetime
from decimal import Decimal
from typing import Optional
from brick_pos.domain.errors import RuleBroken
from brick_pos.domain.values import DepositState
ZERO = Decimal("0.00")
def deposit_ref(shop_id: str, bag_no: str) -> str:
return f"dep:pos:{shop_id}:{bag_no}"
def safe_balance(drops: Decimal, top_ups: Decimal, sealed: Decimal) -> Decimal:
"""What the shop's safe holds: the safe drops, less the float topped up from it, less the bags already sealed. Derived from the
shop's own rows every time it is asked; never a running total."""
return drops - top_ups - sealed
@dataclass
class CashDeposit:
deposit_ref: str
shop_id: str = ""
bag_no: str = ""
amount: Decimal = ZERO
currency: str = ""
company_code: str = ""
bank_account_id: str = ""
expected_on: Optional[date] = None
denominations: str = ""
sealed_by: str = ""
sealed_sub: str = ""
sealed_at: Optional[datetime] = None
status: str = DepositState.SEALED.value
handed_over_at: Optional[datetime] = None
events: list = field(default_factory=list, repr=False, compare=False)
@classmethod
def seal(cls, *, shop_id: str, bag_no: str, amount: Decimal, currency: str, company_code: str, bank_account_id: str,
expected_on: date, denominations: str, safe: Decimal, at: datetime, by: str, sub: str) -> "CashDeposit":
if not company_code:
raise RuleBroken(f"shop {shop_id} has no company: the bank cannot be told whose cash this is", gate="company_code",
fix="set company_code on the shop")
if amount > safe:
raise RuleBroken(f"the safe of {shop_id} holds {safe}: a bag of {amount} is more", gate="safe_balance",
fix="seal at most what the safe holds")
return cls(deposit_ref=deposit_ref(shop_id, bag_no), shop_id=shop_id, bag_no=bag_no, amount=amount, currency=currency,
company_code=company_code, bank_account_id=bank_account_id, expected_on=expected_on, denominations=denominations,
sealed_by=by, sealed_sub=sub, sealed_at=at)
def handed_over(self, at: datetime) -> None:
"""sealed -> handed_over, once payments has the expected batch. Never back."""
if self.status == DepositState.HANDED_OVER.value:
return
self.status, self.handed_over_at = DepositState.HANDED_OVER.value, at