dodil/erp-brick-consolidation
DODIL ERP brick: consolidation
src/brick_consolidation/domain/structure_change.py
"""The StructureChange aggregate — an acquisition, an ownership change or a method change, with what it computed.
The figures (net assets, goodwill, NCI, the rate) are computed in application/structure.py from gl's answers and stored here as they
were shown; approving writes the dated structure row. No database, no HTTP, no identity.
"""
from dataclasses import dataclass, field
from datetime import date
from decimal import Decimal
from typing import Optional
from brick_consolidation.domain.errors import RuleBroken
from brick_consolidation.domain.events import StructureChangeEffective, StructureChangeProposed, StructureChangeRejected
from brick_consolidation.domain.values import StructureChangeState
@dataclass
class StructureChange:
"""an acquisition, an ownership change, a divestiture or a method change, with what it computed"""
change_id: str
kind: str = ""
company_code: str = ""
investor_company_code: str = ""
ownership_pct: Optional[Decimal] = None
voting_pct: Optional[Decimal] = None
method: str = ""
currency: str = ""
effective_date: Optional[date] = None
consideration: Optional[Decimal] = None
proceeds: Optional[Decimal] = None
previously_held_fair_value: Optional[Decimal] = None
net_assets_json: str = ""
fair_value_adjustments_json: str = ""
net_assets: Optional[Decimal] = None
goodwill: Optional[Decimal] = None
nci: Optional[Decimal] = None
gain_loss: Optional[Decimal] = None
rate: Optional[Decimal] = None
goodwill_group: Optional[Decimal] = None
nci_group: Optional[Decimal] = None
bargain_purchase: str = ""
proposed_by: str = ""
proposed_sub: str = ""
approved_by: str = ""
approved_sub: str = ""
status: str = StructureChangeState.PROPOSED.value
figures_json: str = ""
source: str = "gl" # v3: gl (default) | upload
name: str = ""
events: list = field(default_factory=list, repr=False, compare=False)
def _record(self, event):
self.events.append(event)
return event
def proposed(self, by: str = ""):
self.status = StructureChangeState.PROPOSED.value
return self._record(StructureChangeProposed(change_id=self.change_id, by=by))
def approved(self, by: str = ""):
self.status = StructureChangeState.EFFECTIVE.value
return self._record(StructureChangeEffective(change_id=self.change_id, by=by))
def reject_structure_change(self, reason: str, *, by: str = "") -> Optional[StructureChangeRejected]:
"""Reject the structure change: proposed -> rejected."""
it = self
if it.status == StructureChangeState.REJECTED:
return None # already rejected: a repeat changes nothing
if it.status not in (StructureChangeState.PROPOSED,):
raise RuleBroken(f"a {it.status} structure change cannot move to rejected")
if not reason:
raise RuleBroken("reason is required")
it.status = StructureChangeState.REJECTED.value
return self._record(StructureChangeRejected(change_id=it.change_id, by=by))