Implement FIN-WP-0006-T01 settlement statement join; mark FIN-WP-0004/0005 blocked
Add SettlementStatement schema and a dedicated settlement_statements store (ingest_settlement_statement, current_settlement_statements) so resource-control internal transfer settlements join as a referenced projection without ever entering the booked-cost ledger. exchange_health now surfaces a current statement with payment_recognition=unknown as a settlement_awaiting_payment_join residual instead of booked cost. Adds CLI commands finhub ledger ingest-settlement / settlements. FIN-WP-0006-T01 is done; the workplan is finished. Reviewed all other open workplans for closeable work: FIN-WP-0004's only remaining task (T05) waits on an external provider fact from resource-control, and every FIN-WP-0005 task waits on operator/Steuerbüro confirmation or a non-production DATEV tenant. Neither has actionable in-repo work right now, so both move to status: blocked. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: sonnet Assistant-Process: 242919@bnt-lap001 Assistant-Session: 286d235a-1654-40ff-b8fd-eee4e2ff9ea6
This commit is contained in:
parent
4ef6fec8ff
commit
91d62f6430
8 changed files with 377 additions and 8 deletions
|
|
@ -21,8 +21,10 @@ from fin_hub.services.billing import build_billing_basis
|
||||||
from fin_hub.services.effectiveness import effectiveness_report
|
from fin_hub.services.effectiveness import effectiveness_report
|
||||||
from fin_hub.services.evaluate import evaluate_runway
|
from fin_hub.services.evaluate import evaluate_runway
|
||||||
from fin_hub.services.exchange import (
|
from fin_hub.services.exchange import (
|
||||||
|
current_settlement_statements,
|
||||||
ingest_resource_forecast,
|
ingest_resource_forecast,
|
||||||
ingest_resource_usage,
|
ingest_resource_usage,
|
||||||
|
ingest_settlement_statement,
|
||||||
reconcile_resource_period,
|
reconcile_resource_period,
|
||||||
)
|
)
|
||||||
from fin_hub.services.reporting_allocation import (
|
from fin_hub.services.reporting_allocation import (
|
||||||
|
|
@ -304,6 +306,28 @@ def _cmd_ledger_reconcile_resource(args: argparse.Namespace) -> int:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _cmd_ledger_ingest_settlement(args: argparse.Namespace) -> int:
|
||||||
|
payload = json.loads(Path(args.path).read_text(encoding="utf-8"))
|
||||||
|
statement = ingest_settlement_statement(
|
||||||
|
payload,
|
||||||
|
source_evidence_ref=args.source,
|
||||||
|
ledger_path=_ledger_path(args),
|
||||||
|
)
|
||||||
|
print(json.dumps(json.loads(statement.model_dump_json()), indent=2))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _cmd_ledger_settlements(args: argparse.Namespace) -> int:
|
||||||
|
statements = current_settlement_statements(ledger_path=_ledger_path(args))
|
||||||
|
print(
|
||||||
|
json.dumps(
|
||||||
|
[json.loads(statement.model_dump_json()) for statement in statements],
|
||||||
|
indent=2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def _cmd_ledger_allocations(args: argparse.Namespace) -> int:
|
def _cmd_ledger_allocations(args: argparse.Namespace) -> int:
|
||||||
reports = shared_cost_allocations(ledger_path=_ledger_path(args))
|
reports = shared_cost_allocations(ledger_path=_ledger_path(args))
|
||||||
print(json.dumps([report.as_dict() for report in reports], indent=2, default=str))
|
print(json.dumps([report.as_dict() for report in reports], indent=2, default=str))
|
||||||
|
|
@ -442,6 +466,21 @@ def build_parser() -> argparse.ArgumentParser:
|
||||||
ledger_reconcile.add_argument("--ledger", help="Ledger database path")
|
ledger_reconcile.add_argument("--ledger", help="Ledger database path")
|
||||||
ledger_reconcile.set_defaults(func=_cmd_ledger_reconcile_resource)
|
ledger_reconcile.set_defaults(func=_cmd_ledger_reconcile_resource)
|
||||||
|
|
||||||
|
ledger_ingest_settlement = ledger_sub.add_parser(
|
||||||
|
"ingest-settlement",
|
||||||
|
help="Join a resource-control settlement statement (not booked spend)",
|
||||||
|
)
|
||||||
|
ledger_ingest_settlement.add_argument("path")
|
||||||
|
ledger_ingest_settlement.add_argument("--source", required=True)
|
||||||
|
ledger_ingest_settlement.add_argument("--ledger", help="Ledger database path")
|
||||||
|
ledger_ingest_settlement.set_defaults(func=_cmd_ledger_ingest_settlement)
|
||||||
|
|
||||||
|
ledger_settlements = ledger_sub.add_parser(
|
||||||
|
"settlements", help="List current settlement statement projections"
|
||||||
|
)
|
||||||
|
ledger_settlements.add_argument("--ledger", help="Ledger database path")
|
||||||
|
ledger_settlements.set_defaults(func=_cmd_ledger_settlements)
|
||||||
|
|
||||||
ledger_summary = ledger_sub.add_parser("summary", help="Monthly rollup summary from ledger")
|
ledger_summary = ledger_sub.add_parser("summary", help="Monthly rollup summary from ledger")
|
||||||
ledger_summary.add_argument("--ledger", help="Ledger database path (default: .fin-hub/ledger.db)")
|
ledger_summary.add_argument("--ledger", help="Ledger database path (default: .fin-hub/ledger.db)")
|
||||||
ledger_summary.set_defaults(func=_cmd_ledger_summary)
|
ledger_summary.set_defaults(func=_cmd_ledger_summary)
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,78 @@ class FinancialConstraintSignal(BaseModel):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class SettlementStatement(BaseModel):
|
||||||
|
"""Inbound resource-control internal transfer settlement (not booked spend)."""
|
||||||
|
|
||||||
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
|
schema_version: Literal["0.1"] = "0.1"
|
||||||
|
record_type: Literal["settlement_statement"] = "settlement_statement"
|
||||||
|
terms_version: str = Field(min_length=1)
|
||||||
|
financial_entity_id: str = Field(pattern=r"^entity:[a-z0-9]+$")
|
||||||
|
procuring_entity_id: str = Field(pattern=r"^entity:[a-z0-9]+$")
|
||||||
|
period: str
|
||||||
|
statement_date: date
|
||||||
|
due_date: date
|
||||||
|
currency: Literal["EUR"] = "EUR"
|
||||||
|
line_items: list[dict]
|
||||||
|
known_delivered_cost_eur: Decimal
|
||||||
|
known_transfer_price_eur: Decimal
|
||||||
|
unknown_cost_remainder: bool
|
||||||
|
prior_outstanding_eur: Decimal
|
||||||
|
recognized_payments_eur: Decimal | None = None
|
||||||
|
payment_recognition: Literal["unknown", "recognized"]
|
||||||
|
interest_eur: Decimal
|
||||||
|
new_transfer_charges_eur: Decimal
|
||||||
|
outstanding_eur: Decimal
|
||||||
|
credit_limit_eur: Decimal | None = None
|
||||||
|
credit_headroom_eur: Decimal | None = None
|
||||||
|
consumption_mode: Literal["open", "restricted"]
|
||||||
|
next_month_allowance_eur: Decimal | None = None
|
||||||
|
payment_instruction: dict | None = None
|
||||||
|
|
||||||
|
@field_validator("period")
|
||||||
|
@classmethod
|
||||||
|
def validate_period(cls, value: str) -> str:
|
||||||
|
return reporting_month(value)
|
||||||
|
|
||||||
|
@field_validator(
|
||||||
|
"known_delivered_cost_eur",
|
||||||
|
"known_transfer_price_eur",
|
||||||
|
"prior_outstanding_eur",
|
||||||
|
"interest_eur",
|
||||||
|
"new_transfer_charges_eur",
|
||||||
|
"outstanding_eur",
|
||||||
|
mode="before",
|
||||||
|
)
|
||||||
|
@classmethod
|
||||||
|
def validate_nonnegative_money(cls, value):
|
||||||
|
return money(value, non_negative=True)
|
||||||
|
|
||||||
|
@field_validator(
|
||||||
|
"recognized_payments_eur",
|
||||||
|
"credit_limit_eur",
|
||||||
|
"credit_headroom_eur",
|
||||||
|
"next_month_allowance_eur",
|
||||||
|
mode="before",
|
||||||
|
)
|
||||||
|
@classmethod
|
||||||
|
def validate_optional_nonnegative_money(cls, value):
|
||||||
|
return None if value is None else money(value, non_negative=True)
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def validate_dates(self):
|
||||||
|
if self.due_date < self.statement_date:
|
||||||
|
raise ValueError("due_date cannot precede statement_date")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def statement_id(self) -> str:
|
||||||
|
return (
|
||||||
|
f"settlement:{self.financial_entity_id}:{self.procuring_entity_id}:"
|
||||||
|
f"{self.period}:{self.statement_date.isoformat()}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
PlanningEvidence = Annotated[
|
PlanningEvidence = Annotated[
|
||||||
ForecastEvidence
|
ForecastEvidence
|
||||||
| UsageObservation
|
| UsageObservation
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ from fin_hub.schemas.exchange import (
|
||||||
BookedCostEvidence,
|
BookedCostEvidence,
|
||||||
FinancialConstraintSignal,
|
FinancialConstraintSignal,
|
||||||
PlanningEvidence,
|
PlanningEvidence,
|
||||||
|
SettlementStatement,
|
||||||
)
|
)
|
||||||
from fin_hub.services.ledger import _connect, default_ledger_path
|
from fin_hub.services.ledger import _connect, default_ledger_path
|
||||||
|
|
||||||
|
|
@ -297,6 +298,100 @@ def ingest_planning_evidence(
|
||||||
return record
|
return record
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_settlement_schema(conn: sqlite3.Connection) -> None:
|
||||||
|
conn.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS settlement_statements (
|
||||||
|
statement_id TEXT PRIMARY KEY,
|
||||||
|
financial_entity_id TEXT NOT NULL,
|
||||||
|
procuring_entity_id TEXT NOT NULL,
|
||||||
|
period TEXT NOT NULL,
|
||||||
|
payment_recognition TEXT NOT NULL,
|
||||||
|
payload_json TEXT NOT NULL,
|
||||||
|
source_evidence_ref TEXT NOT NULL,
|
||||||
|
is_current INTEGER NOT NULL DEFAULT 1,
|
||||||
|
received_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
conn.execute(
|
||||||
|
"CREATE INDEX IF NOT EXISTS ix_settlement_statements_entity_period "
|
||||||
|
"ON settlement_statements (financial_entity_id, procuring_entity_id, period, is_current)"
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def ingest_settlement_statement(
|
||||||
|
payload: dict,
|
||||||
|
*,
|
||||||
|
source_evidence_ref: str,
|
||||||
|
ledger_path: Path | None = None,
|
||||||
|
) -> SettlementStatement:
|
||||||
|
"""Join a resource-control settlement statement as a referenced projection.
|
||||||
|
|
||||||
|
A settlement statement is never posted as booked spend: it is stored in
|
||||||
|
its own table, outside `ledger_entries`, and carries no path into the
|
||||||
|
booked-fact ledger.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if payload.get("record_type") != "settlement_statement":
|
||||||
|
raise ValueError("payload must have record_type=settlement_statement")
|
||||||
|
if not source_evidence_ref.strip():
|
||||||
|
raise ValueError("source_evidence_ref is required")
|
||||||
|
statement = SettlementStatement.model_validate(payload)
|
||||||
|
statement_id = statement.statement_id()
|
||||||
|
canonical = statement.model_dump_json()
|
||||||
|
ledger = ledger_path or default_ledger_path()
|
||||||
|
with _connect(ledger) as conn:
|
||||||
|
_ensure_settlement_schema(conn)
|
||||||
|
conn.execute("BEGIN IMMEDIATE")
|
||||||
|
existing = conn.execute(
|
||||||
|
"SELECT payload_json FROM settlement_statements WHERE statement_id = ?",
|
||||||
|
(statement_id,),
|
||||||
|
).fetchone()
|
||||||
|
if existing is not None:
|
||||||
|
if json.loads(existing["payload_json"]) != json.loads(canonical):
|
||||||
|
raise ValueError("statement_id already exists with different content")
|
||||||
|
return statement
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE settlement_statements SET is_current = 0 "
|
||||||
|
"WHERE financial_entity_id = ? AND procuring_entity_id = ? AND period = ?",
|
||||||
|
(statement.financial_entity_id, statement.procuring_entity_id, statement.period),
|
||||||
|
)
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO settlement_statements "
|
||||||
|
"(statement_id, financial_entity_id, procuring_entity_id, period, "
|
||||||
|
"payment_recognition, payload_json, source_evidence_ref, is_current) "
|
||||||
|
"VALUES (?, ?, ?, ?, ?, ?, ?, 1)",
|
||||||
|
(
|
||||||
|
statement_id,
|
||||||
|
statement.financial_entity_id,
|
||||||
|
statement.procuring_entity_id,
|
||||||
|
statement.period,
|
||||||
|
statement.payment_recognition,
|
||||||
|
canonical,
|
||||||
|
source_evidence_ref,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
return statement
|
||||||
|
|
||||||
|
|
||||||
|
def current_settlement_statements(
|
||||||
|
*, ledger_path: Path | None = None
|
||||||
|
) -> list[SettlementStatement]:
|
||||||
|
"""Return current, referenced settlement statement projections."""
|
||||||
|
|
||||||
|
ledger = ledger_path or default_ledger_path()
|
||||||
|
with _connect(ledger) as conn:
|
||||||
|
_ensure_settlement_schema(conn)
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT payload_json FROM settlement_statements "
|
||||||
|
"WHERE is_current = 1 ORDER BY statement_id"
|
||||||
|
).fetchall()
|
||||||
|
return [SettlementStatement.model_validate_json(row["payload_json"]) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
def booked_cost_projection(
|
def booked_cost_projection(
|
||||||
*,
|
*,
|
||||||
ledger_path: Path | None = None,
|
ledger_path: Path | None = None,
|
||||||
|
|
@ -473,6 +568,21 @@ def exchange_health(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for statement in current_settlement_statements(ledger_path=ledger):
|
||||||
|
if statement.payment_recognition == "unknown":
|
||||||
|
issues.append(
|
||||||
|
ExchangeQualityIssue(
|
||||||
|
code="settlement_awaiting_payment_join",
|
||||||
|
severity="warning",
|
||||||
|
record_id=statement.statement_id(),
|
||||||
|
detail=(
|
||||||
|
f"{statement.outstanding_eur} EUR outstanding for "
|
||||||
|
f"{statement.financial_entity_id} {statement.period} has no "
|
||||||
|
"recognized payment join; this is a residual, not booked cost"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return ExchangeHealthReport(
|
return ExchangeHealthReport(
|
||||||
schema_version="0.1",
|
schema_version="0.1",
|
||||||
artifact_type="resource_cost_exchange_health",
|
artifact_type="resource_cost_exchange_health",
|
||||||
|
|
|
||||||
28
tests/fixtures/settlement-statement-2026-08.json
vendored
Normal file
28
tests/fixtures/settlement-statement-2026-08.json
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"schema_version": "0.1",
|
||||||
|
"record_type": "settlement_statement",
|
||||||
|
"terms_version": "V0.1",
|
||||||
|
"financial_entity_id": "entity:frontier",
|
||||||
|
"procuring_entity_id": "entity:railiance",
|
||||||
|
"period": "2026-08",
|
||||||
|
"statement_date": "2026-09-01",
|
||||||
|
"due_date": "2026-09-15",
|
||||||
|
"currency": "EUR",
|
||||||
|
"line_items": [
|
||||||
|
{"resource_id": "resource:platform:audit-storage", "delivered_cost_eur": "42.10", "markup_eur": "6.32"}
|
||||||
|
],
|
||||||
|
"known_delivered_cost_eur": "42.10",
|
||||||
|
"known_transfer_price_eur": "48.42",
|
||||||
|
"unknown_cost_remainder": false,
|
||||||
|
"prior_outstanding_eur": "0.00",
|
||||||
|
"recognized_payments_eur": null,
|
||||||
|
"payment_recognition": "unknown",
|
||||||
|
"interest_eur": "0.00",
|
||||||
|
"new_transfer_charges_eur": "48.42",
|
||||||
|
"outstanding_eur": "48.42",
|
||||||
|
"credit_limit_eur": "500.00",
|
||||||
|
"credit_headroom_eur": "451.58",
|
||||||
|
"consumption_mode": "open",
|
||||||
|
"next_month_allowance_eur": null,
|
||||||
|
"payment_instruction": null
|
||||||
|
}
|
||||||
|
|
@ -12,13 +12,16 @@ from fin_hub.schemas.exchange import (
|
||||||
BookedCostEvidence,
|
BookedCostEvidence,
|
||||||
ForecastEvidence,
|
ForecastEvidence,
|
||||||
)
|
)
|
||||||
|
from fin_hub.schemas.exchange import SettlementStatement
|
||||||
from fin_hub.services.exchange import (
|
from fin_hub.services.exchange import (
|
||||||
booked_cost_projection,
|
booked_cost_projection,
|
||||||
|
current_settlement_statements,
|
||||||
exchange_health,
|
exchange_health,
|
||||||
financial_constraint_projection,
|
financial_constraint_projection,
|
||||||
ingest_planning_evidence,
|
ingest_planning_evidence,
|
||||||
ingest_resource_forecast,
|
ingest_resource_forecast,
|
||||||
ingest_resource_usage,
|
ingest_resource_usage,
|
||||||
|
ingest_settlement_statement,
|
||||||
reconcile_resource_period,
|
reconcile_resource_period,
|
||||||
)
|
)
|
||||||
from fin_hub.services.ledger import import_csv
|
from fin_hub.services.ledger import import_csv
|
||||||
|
|
@ -413,3 +416,86 @@ def test_audit_storage_usage_omits_nulls_and_does_not_invent_zero_spend(tmp_path
|
||||||
assert august.invented_zero is False
|
assert august.invented_zero is False
|
||||||
health = exchange_health(ledger_path=ledger)
|
health = exchange_health(ledger_path=ledger)
|
||||||
assert "usage_without_booked_fact" in {issue.code for issue in health.issues}
|
assert "usage_without_booked_fact" in {issue.code for issue in health.issues}
|
||||||
|
|
||||||
|
|
||||||
|
def _settlement_payload() -> dict:
|
||||||
|
return json.loads((FIXTURES / "settlement-statement-2026-08.json").read_text())
|
||||||
|
|
||||||
|
|
||||||
|
def test_settlement_statement_joins_as_referenced_projection_not_booked_cost(
|
||||||
|
tmp_path: Path,
|
||||||
|
):
|
||||||
|
ledger = tmp_path / "ledger.db"
|
||||||
|
payload = _settlement_payload()
|
||||||
|
|
||||||
|
statement = ingest_settlement_statement(
|
||||||
|
payload,
|
||||||
|
source_evidence_ref="resource-control:data/settlements/2026-08.json",
|
||||||
|
ledger_path=ledger,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert isinstance(statement, SettlementStatement)
|
||||||
|
assert statement.financial_entity_id == "entity:frontier"
|
||||||
|
assert statement.outstanding_eur == Decimal("48.42")
|
||||||
|
assert statement.payment_recognition == "unknown"
|
||||||
|
|
||||||
|
current = current_settlement_statements(ledger_path=ledger)
|
||||||
|
assert len(current) == 1
|
||||||
|
assert current[0].statement_id() == statement.statement_id()
|
||||||
|
|
||||||
|
# No booked cost is created by joining a settlement statement.
|
||||||
|
assert booked_cost_projection(ledger_path=ledger) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_settlement_statement_is_idempotent_and_supersedes_prior_period_statement(
|
||||||
|
tmp_path: Path,
|
||||||
|
):
|
||||||
|
ledger = tmp_path / "ledger.db"
|
||||||
|
payload = _settlement_payload()
|
||||||
|
|
||||||
|
ingest_settlement_statement(
|
||||||
|
payload, source_evidence_ref="resource-control:data/settlements/2026-08.json",
|
||||||
|
ledger_path=ledger,
|
||||||
|
)
|
||||||
|
ingest_settlement_statement(
|
||||||
|
payload, source_evidence_ref="resource-control:data/settlements/2026-08.json",
|
||||||
|
ledger_path=ledger,
|
||||||
|
)
|
||||||
|
assert len(current_settlement_statements(ledger_path=ledger)) == 1
|
||||||
|
|
||||||
|
corrected = {**payload, "statement_date": "2026-09-05", "outstanding_eur": "10.00",
|
||||||
|
"new_transfer_charges_eur": "10.00", "known_transfer_price_eur": "10.00"}
|
||||||
|
ingest_settlement_statement(
|
||||||
|
corrected, source_evidence_ref="resource-control:data/settlements/2026-08-corrected.json",
|
||||||
|
ledger_path=ledger,
|
||||||
|
)
|
||||||
|
|
||||||
|
current = current_settlement_statements(ledger_path=ledger)
|
||||||
|
assert len(current) == 1
|
||||||
|
assert current[0].outstanding_eur == Decimal("10.00")
|
||||||
|
|
||||||
|
|
||||||
|
def test_settlement_statement_rejects_provider_invoice_shaped_payload(tmp_path: Path):
|
||||||
|
ledger = tmp_path / "ledger.db"
|
||||||
|
with pytest.raises(ValueError, match="record_type=settlement_statement"):
|
||||||
|
ingest_settlement_statement(
|
||||||
|
{"record_type": "booked_cost"},
|
||||||
|
source_evidence_ref="resource-control:x",
|
||||||
|
ledger_path=ledger,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_exchange_health_surfaces_settlement_without_payment_join_as_residual(
|
||||||
|
tmp_path: Path,
|
||||||
|
):
|
||||||
|
ledger = tmp_path / "ledger.db"
|
||||||
|
ingest_settlement_statement(
|
||||||
|
_settlement_payload(),
|
||||||
|
source_evidence_ref="resource-control:data/settlements/2026-08.json",
|
||||||
|
ledger_path=ledger,
|
||||||
|
)
|
||||||
|
|
||||||
|
health = exchange_health(ledger_path=ledger)
|
||||||
|
|
||||||
|
assert "settlement_awaiting_payment_join" in {issue.code for issue in health.issues}
|
||||||
|
assert health.booked_fact_count == 0
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ type: workplan
|
||||||
title: "Establish the resource cost evidence contract"
|
title: "Establish the resource cost evidence contract"
|
||||||
domain: infotech
|
domain: infotech
|
||||||
repo: fin-hub
|
repo: fin-hub
|
||||||
status: active
|
status: blocked
|
||||||
flavor: implementation
|
flavor: implementation
|
||||||
owner: codex
|
owner: codex
|
||||||
topic_slug: financials
|
topic_slug: financials
|
||||||
created: "2026-08-10"
|
created: "2026-08-10"
|
||||||
updated: "2026-08-15"
|
updated: "2026-09-27"
|
||||||
related:
|
related:
|
||||||
- FIN-WP-0001
|
- FIN-WP-0001
|
||||||
- RESOURCE-WP-0002
|
- RESOURCE-WP-0002
|
||||||
|
|
@ -208,6 +208,13 @@ reconciles as usage present, `missing_booked_fact: true`,
|
||||||
`docs/evidence/fin-wp-0004-t05-commissioning-2026-08.md`. T05 still
|
`docs/evidence/fin-wp-0004-t05-commissioning-2026-08.md`. T05 still
|
||||||
waits on the first Scaleway charge or credit (expected 2026-09).
|
waits on the first Scaleway charge or credit (expected 2026-09).
|
||||||
|
|
||||||
|
Status 2026-09-27: reviewed the workplan for closeable work. T05 is the
|
||||||
|
only open task and remains genuinely blocked on an external event (the
|
||||||
|
first real `platform:audit-storage` provider charge or credit) that
|
||||||
|
resource-control has not yet published; there is no remaining code or
|
||||||
|
contract work in this repo to advance it. Set workplan `status: blocked`
|
||||||
|
to reflect that nothing here is actionable until that fact arrives.
|
||||||
|
|
||||||
## T06 — Generalize and operate the contract
|
## T06 — Generalize and operate the contract
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ type: workplan
|
||||||
title: "DATEV accounting adapter operations"
|
title: "DATEV accounting adapter operations"
|
||||||
domain: financials
|
domain: financials
|
||||||
repo: fin-hub
|
repo: fin-hub
|
||||||
status: active
|
status: blocked
|
||||||
flavor: implementation
|
flavor: implementation
|
||||||
owner: codex
|
owner: codex
|
||||||
topic_slug: financials
|
topic_slug: financials
|
||||||
created: "2026-08-11"
|
created: "2026-08-11"
|
||||||
updated: "2026-08-11"
|
updated: "2026-09-27"
|
||||||
related:
|
related:
|
||||||
- FIN-WP-0002
|
- FIN-WP-0002
|
||||||
state_hub_workstream_id: "58432770-da19-5b72-a946-cacbe1eb24ca"
|
state_hub_workstream_id: "58432770-da19-5b72-a946-cacbe1eb24ca"
|
||||||
|
|
@ -31,7 +31,7 @@ invoice numbers, execute payments, or perform bookkeeping.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: FIN-WP-0005-T01
|
id: FIN-WP-0005-T01
|
||||||
status: todo
|
status: wait
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "7cdda85d-cdd6-544c-b873-f27ec7ef448d"
|
state_hub_task_id: "7cdda85d-cdd6-544c-b873-f27ec7ef448d"
|
||||||
```
|
```
|
||||||
|
|
@ -42,6 +42,13 @@ retention policy, correction procedure, and required structured fields.
|
||||||
Record which capabilities use Qonto's managed DATEV services and which require
|
Record which capabilities use Qonto's managed DATEV services and which require
|
||||||
a direct DATEV integration. Route credentials only after this selection.
|
a direct DATEV integration. Route credentials only after this selection.
|
||||||
|
|
||||||
|
Status 2026-09-27: reviewed for closeable work. T01 requires a live
|
||||||
|
confirmation from the operator and Steuerbüro (plan/tenant selection,
|
||||||
|
master-data ownership, retention and correction policy) that no in-repo
|
||||||
|
change can substitute for; set to `wait` pending that conversation. T02-T04
|
||||||
|
already wait on T01 and a non-production DATEV tenant, so no task in this
|
||||||
|
workplan is actionable from the repo alone right now.
|
||||||
|
|
||||||
## Implement the selected transport
|
## Implement the selected transport
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ type: workplan
|
||||||
title: "Consume resource-control internal transfer settlement"
|
title: "Consume resource-control internal transfer settlement"
|
||||||
domain: financials
|
domain: financials
|
||||||
repo: fin-hub
|
repo: fin-hub
|
||||||
status: proposed
|
status: finished
|
||||||
flavor: residual
|
flavor: residual
|
||||||
owner: codex
|
owner: codex
|
||||||
topic_slug: financials
|
topic_slug: financials
|
||||||
created: "2026-08-14"
|
created: "2026-08-14"
|
||||||
updated: "2026-08-14"
|
updated: "2026-09-27"
|
||||||
related:
|
related:
|
||||||
- FIN-WP-0004
|
- FIN-WP-0004
|
||||||
- RESOURCE-WP-0005
|
- RESOURCE-WP-0005
|
||||||
|
|
@ -43,7 +43,7 @@ Origin: `RESOURCE-WP-0005-T07`. Terms live in
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: FIN-WP-0006-T01
|
id: FIN-WP-0006-T01
|
||||||
status: todo
|
status: done
|
||||||
flavor: residual
|
flavor: residual
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "9ef3fab5-51a6-57ef-8121-b9714298c502"
|
state_hub_task_id: "9ef3fab5-51a6-57ef-8121-b9714298c502"
|
||||||
|
|
@ -57,3 +57,23 @@ a provider invoice.
|
||||||
Done when an inbound fixture can be stored as a referenced projection
|
Done when an inbound fixture can be stored as a referenced projection
|
||||||
and `exchange_health` shows statements lacking a payment join as a
|
and `exchange_health` shows statements lacking a payment join as a
|
||||||
visible residual, not as booked cost.
|
visible residual, not as booked cost.
|
||||||
|
|
||||||
|
Completed 2026-09-27: added `SettlementStatement`
|
||||||
|
(`src/fin_hub/schemas/exchange.py`) matching resource-control's v0.1
|
||||||
|
`settlement-statement.schema.json`, with a dedicated
|
||||||
|
`settlement_statements` table (`services/exchange.py`) that never touches
|
||||||
|
`ledger_entries`. `ingest_settlement_statement` validates and stores each
|
||||||
|
statement keyed by `financial_entity_id` + `procuring_entity_id` + `period`
|
||||||
|
+ `statement_date`, idempotent on identical resubmission, and superseding
|
||||||
|
the prior current statement for the same entity/period pair on a later
|
||||||
|
`statement_date`. `exchange_health` now emits
|
||||||
|
`settlement_awaiting_payment_join` (severity `warning`) for any current
|
||||||
|
statement with `payment_recognition: "unknown"`, listing its outstanding
|
||||||
|
amount as a residual rather than booked cost.
|
||||||
|
`current_settlement_statements` exposes the stored projection for
|
||||||
|
reporting. CLI: `finhub ledger ingest-settlement` and
|
||||||
|
`finhub ledger settlements`. Fixture:
|
||||||
|
`tests/fixtures/settlement-statement-2026-08.json`. Tests:
|
||||||
|
`tests/test_exchange.py::test_settlement_statement_*` and
|
||||||
|
`::test_exchange_health_surfaces_settlement_without_payment_join_as_residual`.
|
||||||
|
Full suite: 102 passed.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue