mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-07-01 03:04:52 +02:00
310 lines
11 KiB
Python
310 lines
11 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import date, datetime
|
|
from decimal import Decimal
|
|
from typing import Any
|
|
|
|
|
|
def _iso_now() -> str:
|
|
return datetime.now().astimezone().isoformat(timespec="seconds")
|
|
|
|
|
|
MEMBERSHIP_STATUS_LABELS = {
|
|
"application": "ANTRAG",
|
|
"accepted_pending_payment": "ANGENOMMEN / ZAHLUNG OFFEN",
|
|
"active": "AKTIV",
|
|
"suspended_contribution": "RUHEND / BEITRAG",
|
|
"resigned_end_of_year": "AUSTRITT ZUM JAHRESENDE",
|
|
"honorary": "EHRENMITGLIED",
|
|
"ended": "BEENDET",
|
|
}
|
|
|
|
ASSET_STATUS_LABELS = {
|
|
"available": "VERFUEGBAR",
|
|
"issued": "AUSGEGEBEN",
|
|
"lost": "VERLOREN",
|
|
"retired": "AUSGEMUSTERT",
|
|
}
|
|
|
|
HOUSEKEEPER_MEMBER_FIELD_LABELS = {
|
|
"nickname": "Nickname",
|
|
"email": "E-Mail-Adresse",
|
|
"phone": "Telefonnummer",
|
|
"birth_date": "Geburtsdatum",
|
|
"street": "Straße und Hausnummer",
|
|
"postal_code": "Postleitzahl",
|
|
"city": "Ort",
|
|
"country": "Land",
|
|
"accepted_at": "Aufnahmebeschluss",
|
|
"membership_started_at": "Mitglied seit",
|
|
}
|
|
|
|
DEFAULT_OPTIONAL_MEMBER_FIELDS = tuple(
|
|
field for field in HOUSEKEEPER_MEMBER_FIELD_LABELS if field != "birth_date"
|
|
)
|
|
|
|
|
|
def normalize_optional_member_fields(values: Any) -> tuple[str, ...]:
|
|
if not isinstance(values, (list, tuple, set, frozenset)):
|
|
return ()
|
|
selected = {str(value).strip() for value in values}
|
|
return tuple(field for field in HOUSEKEEPER_MEMBER_FIELD_LABELS if field in selected)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class Member:
|
|
member_id: str
|
|
member_number: str
|
|
first_name: str
|
|
last_name: str
|
|
nickname: str = ""
|
|
email: str = ""
|
|
phone: str = ""
|
|
birth_date: str = ""
|
|
street: str = ""
|
|
address_addition: str = ""
|
|
postal_code: str = ""
|
|
city: str = ""
|
|
country: str = "Deutschland"
|
|
account_holder: str = ""
|
|
iban: str = ""
|
|
bic: str = ""
|
|
mandate_reference: str = ""
|
|
mandate_signed_at: str = ""
|
|
mandate_active: bool = False
|
|
mandate_revoked_at: str = ""
|
|
status: str = "application"
|
|
accepted_at: str = ""
|
|
membership_started_at: str = ""
|
|
payment_frequency: str = "annual"
|
|
contribution_rule_id: str = "standard-2022"
|
|
honorary: bool = False
|
|
notes: str = ""
|
|
created_at: str = field(default_factory=_iso_now)
|
|
updated_at: str = field(default_factory=_iso_now)
|
|
schema_version: int = 1
|
|
|
|
@property
|
|
def display_name(self) -> str:
|
|
return " ".join(part for part in (self.first_name, self.last_name) if part).strip()
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"schema_version": self.schema_version,
|
|
"member_id": self.member_id,
|
|
"member_number": self.member_number,
|
|
"person": {
|
|
"first_name": self.first_name,
|
|
"last_name": self.last_name,
|
|
"nickname": self.nickname,
|
|
"birth_date": self.birth_date,
|
|
"email": self.email,
|
|
"phone": self.phone,
|
|
},
|
|
"address": {
|
|
"street": self.street,
|
|
"addition": self.address_addition,
|
|
"postal_code": self.postal_code,
|
|
"city": self.city,
|
|
"country": self.country,
|
|
},
|
|
"banking": {
|
|
"account_holder": self.account_holder,
|
|
"iban": self.iban,
|
|
"bic": self.bic,
|
|
"mandate_reference": self.mandate_reference,
|
|
"mandate_signed_at": self.mandate_signed_at,
|
|
"mandate_active": self.mandate_active,
|
|
"mandate_revoked_at": self.mandate_revoked_at,
|
|
},
|
|
"membership": {
|
|
"status": self.status,
|
|
"accepted_at": self.accepted_at,
|
|
"started_at": self.membership_started_at,
|
|
"honorary": self.honorary,
|
|
},
|
|
"contribution_profile": {
|
|
"rule_id": self.contribution_rule_id,
|
|
"payment_frequency": self.payment_frequency,
|
|
},
|
|
"notes": self.notes,
|
|
"created_at": self.created_at,
|
|
"updated_at": self.updated_at,
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any]) -> Member:
|
|
person = data.get("person") or {}
|
|
membership = data.get("membership") or {}
|
|
contribution = data.get("contribution_profile") or {}
|
|
address = data.get("address") or {}
|
|
banking = data.get("banking") or {}
|
|
return cls(
|
|
schema_version=int(data.get("schema_version", 1)),
|
|
member_id=str(data["member_id"]),
|
|
member_number=str(data.get("member_number", "")),
|
|
first_name=str(person.get("first_name", "")),
|
|
last_name=str(person.get("last_name", "")),
|
|
nickname=str(person.get("nickname", "")),
|
|
email=str(person.get("email", "")),
|
|
phone=str(person.get("phone", "")),
|
|
birth_date=str(person.get("birth_date", "")),
|
|
street=str(address.get("street", "")),
|
|
address_addition=str(address.get("addition", "")),
|
|
postal_code=str(address.get("postal_code", "")),
|
|
city=str(address.get("city", "")),
|
|
country=str(address.get("country", "Deutschland")),
|
|
account_holder=str(banking.get("account_holder", "")),
|
|
iban=str(banking.get("iban", "")),
|
|
bic=str(banking.get("bic", "")),
|
|
mandate_reference=str(banking.get("mandate_reference", "")),
|
|
mandate_signed_at=str(banking.get("mandate_signed_at", "")),
|
|
mandate_active=bool(banking.get("mandate_active", False)),
|
|
mandate_revoked_at=str(banking.get("mandate_revoked_at", "")),
|
|
status=str(membership.get("status", "application")),
|
|
accepted_at=str(membership.get("accepted_at", "")),
|
|
membership_started_at=str(membership.get("started_at", "")),
|
|
honorary=bool(membership.get("honorary", False)),
|
|
contribution_rule_id=str(contribution.get("rule_id", "standard-2022")),
|
|
payment_frequency=str(contribution.get("payment_frequency", "annual")),
|
|
notes=str(data.get("notes", "")),
|
|
created_at=str(data.get("created_at", _iso_now())),
|
|
updated_at=str(data.get("updated_at", _iso_now())),
|
|
)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class Asset:
|
|
asset_id: str
|
|
label: str
|
|
category: str = ""
|
|
inventory_number: str = ""
|
|
serial_number: str = ""
|
|
status: str = "available"
|
|
current_holder_member_id: str = ""
|
|
deposit_amount_default: str = "0.00"
|
|
notes: str = ""
|
|
created_at: str = field(default_factory=_iso_now)
|
|
updated_at: str = field(default_factory=_iso_now)
|
|
schema_version: int = 1
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"schema_version": self.schema_version,
|
|
"asset_id": self.asset_id,
|
|
"label": self.label,
|
|
"category": self.category,
|
|
"inventory_number": self.inventory_number,
|
|
"serial_number": self.serial_number,
|
|
"status": self.status,
|
|
"current_holder_member_id": self.current_holder_member_id,
|
|
"deposit_amount_default": self.deposit_amount_default,
|
|
"notes": self.notes,
|
|
"created_at": self.created_at,
|
|
"updated_at": self.updated_at,
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any]) -> Asset:
|
|
return cls(
|
|
schema_version=int(data.get("schema_version", 1)),
|
|
asset_id=str(data["asset_id"]),
|
|
label=str(data.get("label", "")),
|
|
category=str(data.get("category", "")),
|
|
inventory_number=str(data.get("inventory_number", "")),
|
|
serial_number=str(data.get("serial_number", "")),
|
|
status=str(data.get("status", "available")),
|
|
current_holder_member_id=str(data.get("current_holder_member_id", "")),
|
|
deposit_amount_default=str(data.get("deposit_amount_default", "0.00")),
|
|
notes=str(data.get("notes", "")),
|
|
created_at=str(data.get("created_at", _iso_now())),
|
|
updated_at=str(data.get("updated_at", _iso_now())),
|
|
)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class Event:
|
|
event_id: str
|
|
timestamp: str
|
|
event_type: str
|
|
summary: str
|
|
actor_type: str = "system"
|
|
actor_name: str = "CCMA"
|
|
references: dict[str, str] = field(default_factory=dict)
|
|
data: dict[str, Any] = field(default_factory=dict)
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"schema_version": 1,
|
|
"event_id": self.event_id,
|
|
"timestamp": self.timestamp,
|
|
"type": self.event_type,
|
|
"actor": {"type": self.actor_type, "name": self.actor_name},
|
|
"summary": self.summary,
|
|
"references": self.references,
|
|
"data": self.data,
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any]) -> Event:
|
|
actor = data.get("actor") or {}
|
|
return cls(
|
|
event_id=str(data["event_id"]),
|
|
timestamp=str(data["timestamp"]),
|
|
event_type=str(data.get("type", "unknown")),
|
|
summary=str(data.get("summary", "")),
|
|
actor_type=str(actor.get("type", "system")),
|
|
actor_name=str(actor.get("name", "CCMA")),
|
|
references=dict(data.get("references") or {}),
|
|
data=dict(data.get("data") or {}),
|
|
)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class ContributionData:
|
|
claims: list[dict[str, Any]] = field(default_factory=list)
|
|
payments: list[dict[str, Any]] = field(default_factory=list)
|
|
credits: list[dict[str, Any]] = field(default_factory=list)
|
|
allocations: list[dict[str, Any]] = field(default_factory=list)
|
|
reminders: list[dict[str, Any]] = field(default_factory=list)
|
|
schema_version: int = 1
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"schema_version": self.schema_version,
|
|
"claims": self.claims,
|
|
"payments": self.payments,
|
|
"credits": self.credits,
|
|
"allocations": self.allocations,
|
|
"reminders": self.reminders,
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any]) -> ContributionData:
|
|
return cls(
|
|
schema_version=int(data.get("schema_version", 1)),
|
|
claims=list(data.get("claims") or []),
|
|
payments=list(data.get("payments") or []),
|
|
credits=list(data.get("credits") or []),
|
|
allocations=list(data.get("allocations") or []),
|
|
reminders=list(data.get("reminders") or []),
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class HousekeeperFinding:
|
|
severity: str
|
|
code: str
|
|
title: str
|
|
detail: str
|
|
member_id: str = ""
|
|
asset_id: str = ""
|
|
target_type: str = "member"
|
|
due_date: date | None = None
|
|
key: str = ""
|
|
|
|
|
|
def money(value: str | int | float | Decimal) -> Decimal:
|
|
return Decimal(str(value)).quantize(Decimal("0.01"))
|