dodil/erp-brick-contracts

src/brick_contracts/application/use_cases.py

"""The use cases — what contract admins, owners, approvers, our signatories, the counterparty's signers (through the
public door), calling modules and the clock can do, each declared ONCE with @CT.use_case and served as an API route
AND an MCP tool.

`caller` is the first parameter of every one; row visibility is decided inside the use case from it (support.View),
so an agent sees exactly what its person sees. Refusals are Refused(status, reason): a broken rule 409, invalid input
422, a row the caller may not see 404 — never 403, which would confirm it exists.

The modules, by area (each imports CT from here and registers its use cases):
  library        keep_contract_type, keep_clause, amend_clause, retire_clause, keep_template, approve_template,
                 retire_template, keep_settings, install_contract_pack, clause_library, show_template, show_settings
  drafting       draft_contract, edit_draft, set_variables, choose_position, override_section, add_signer,
                 edit_signer, request_approval, decide_approval, approvals_queue
  signing        send_for_signature, recall, reissue_link, copy_signing_link, mark_delivered_by_hand, open_as_us,
                 sign_as_us, decline_as_us, to_sign — and the public commands the door calls (open_for_signing,
                 consent_and_sign, decline, request_changes; public_pages.py)
  lifecycle      upload_signed_elsewhere, cancel, amend_contract, renew_contract, terminate_contract,
                 record_administrative_change, complete_key_date, run_due (the tick), purge_by_retention
  queries        list_contracts, show_contract, show_versions, download_version, show_certificate, verify_evidence,
                 key_dates, contract_status, fetch_executed
  capabilities   contracts.draft / send / status / fetch_executed, PROVIDED to other bricks (hr; crm's adapter)
"""
import os
from contextlib import contextmanager

from erp_platform.bricks import Brick

VERSION = "0.2.0"

CT = Brick("contracts", namespace="contract", version=VERSION, requires=("mail.send",), ids_as_strings=True, instructions=(
    "Contracts drafted from approved templates and a clause library (Preferred / Alternate / Fallback positions), "
    "filled from typed variables rather than re-typed, approved when they deviate (the approval binds to the draft's "
    "fingerprint; an edit voids it), frozen as version N with its sha256 at send, and signed with a SIMPLE electronic "
    "signature: the counterparty through a public link, our side inside the app as their own sign-in. Every signature "
    "event is append-only and hash-chained; the executed copy is the signed version plus a certificate page with its "
    "own sha256. The module only ever says 'signed electronically'. A deed is never signed online: it is signed on "
    "paper and the executed copy uploaded (SIGNED_ELSEWHERE). The counterparty is always a business partner (bp_id). "
    "contract_id is 'ct-' + 16 hex derived from source_ref|type_code. Employment contracts (they hold pay) show their "
    "body only to contract:employment:read; everyone else who may see them sees state and key dates."))


@contextmanager
def _repository_default():
    from erp_platform import db
    from brick_contracts.infrastructure.tables import ContractsRepository
    with db.engine.begin() as conn:
        yield ContractsRepository(conn)


# The unit of work: ONE connection, one transaction. Tests and the scenario harness swap it for SQLite.
repository = _repository_default


class DemoClockOnLive(RuntimeError):
    pass


# The demo clock: the route exists only when CT_DEMO_CLOCK=enabled, and the app refuses to start with it on a live
# deployment (DODIL_ENV=live|prod). Mount it on ONE brick of a composition (hr mounts its own).
if os.environ.get("CT_DEMO_CLOCK", "").strip().lower() == "enabled":
    if os.environ.get("DODIL_ENV", "").strip().lower() in ("live", "prod"):
        raise DemoClockOnLive("CT_DEMO_CLOCK=enabled on a live deployment (DODIL_ENV=live|prod): refusing to start")
    from erp_platform import democlock
    democlock.mount(CT)


from brick_contracts.application import (capabilities, drafting, library, lifecycle, public_pages,  # noqa: E402,F401
                                         queries, signing)

__all__ = ["CT", "repository", "VERSION"]