dodil/erp-brick-contracts

src/brick_contracts/domain/packs/gb.py

"""The gb pack — Great Britain: seed rows and the UK checks (modules/contracts localisation_packs.gb).

CHECKS (in code; each says where it comes from):
  era_s1         Employment Rights Act 1996 s.1: the principal statement's day-one particulars are REQUIRED values
                 (missing[] at draft), and PRINCIPAL_STATEMENT_DUE = start_date is a key date. The duty is to GIVE the
                 statement, so contracts.status warns when the start date arrives and the employee has not been given
                 it (delivered or opened) — issued counts, not only signed (SC-CT-15).
  ir35           ITEPA 2003 Part 2 Chapter 10 (off-payroll working): a medium or large (or public-sector) client
                 deciding OUTSIDE must have given the status determination statement; send is refused without
                 sds_issued_on (SC-CT-14). client_size_regime comes from the CALLER (hr's setting per legal entity);
                 contracts keeps no copy of the size.
  ip_assignment  CDPA 1988 s.90(3): an assignment of copyright is not effective unless signed by the assignor — the
                 counterparty must be a signer.
  deeds          Law Com No 386: a witness to a deed must be physically present — a DEED type is never signed online
                 (contract_type.py).
RETENTION: term + 6 years (Limitation Act 1980 s.5), deeds 12 (s.8).
"""
from datetime import date

PACK = "gb"

# The ERA 1996 s.1 day-one particulars → the variable that carries each (a settings row `era_s1_variables` may
# rename them for a customer's templates). The defaults use hr's names where hr supplies the value.
ERA_S1 = (
    ("the employer's name", "our_name"),
    ("the employee's name", "legal_name"),
    ("job title or a brief description of the work", "job_title"),
    ("the start date", "start_date"),
    ("the date continuous employment began", "continuous_employment_date"),
    ("pay", "rate_amount"),
    ("the pay interval", "rate_basis"),
    ("hours and days of work, and how they may vary", "hours"),
    ("holiday entitlement", "holiday_entitlement"),
    ("the place of work", "place_of_work"),
    ("expected duration, or the end of a fixed term", "duration"),
    ("any probation period", "probation"),
    ("other benefits", "benefits"),
    ("training the employer requires", "training"),
    ("sick pay (may be in a separate accessible document)", "sick_pay"),
    ("other paid leave (may be in a separate accessible document)", "other_paid_leave"),
    ("notice periods (may be in a separate accessible document)", "notice"),
)
SIZE_NEEDS_SDS = ("MEDIUM_LARGE", "PUBLIC")


def era_variables(config) -> list:
    mapped = config.json_setting("era_s1_variables") if config is not None else None
    if isinstance(mapped, dict):
        return [mapped.get(var, var) for _, var in ERA_S1]
    return [var for _, var in ERA_S1]


def draft_missing(ctype, variables: dict, config=None) -> list:
    if "era_s1" not in ctype.pack_checks:
        return []
    return [v for v in era_variables(config) if v not in variables]


def draft_key_dates(ctype, contract, config) -> None:
    if "era_s1" in ctype.pack_checks and contract.starts_on:
        contract.ensure_key_date("PRINCIPAL_STATEMENT_DUE", contract.starts_on, contract.owner_group or contract.owner,
                                 config.key_date_cadence("PRINCIPAL_STATEMENT_DUE") if config else (14, 7, 1))


def send_refusals(ctype, contract) -> list:
    out = []
    v = contract.variables
    if "ir35" in ctype.pack_checks:
        regime = v["client_size_regime"].value if "client_size_regime" in v else None
        det = v["ir35_determination"].value if "ir35_determination" in v else None
        if regime in SIZE_NEEDS_SDS and det == "OUTSIDE" and "sds_issued_on" not in v:
            out.append("an outside determination by a medium or large client needs the status determination "
                       "statement recorded first (sds_issued_on)")
    if "ip_assignment" in ctype.pack_checks or any(s.family in ("ip_assignment", "ip") for s in contract.sections):
        if not any(s.side == "THEM" for s in contract.signers):
            out.append("an assignment of copyright needs the assignor's own signature (CDPA 1988 s.90(3)) — name "
                       "the counterparty as a signer")
    return out


def status_warnings(ctype, contract, today: date) -> list:
    out = []
    if "era_s1" in ctype.pack_checks and contract.principal_statement_issued_at is None:
        k = next((x for x in contract.key_dates if x.kind == "PRINCIPAL_STATEMENT_DUE" and x.done_at is None), None)
        if k is not None and k.due_on <= today:
            when = "is due today" if k.due_on == today else f"was due on {k.due_on.isoformat()}"
            out.append(f"the written statement has not been given and {when}")
    return out


CONSENT_V1 = ("I agree to sign this document electronically. Typing my name and pressing Sign is my signature; a "
              "record of when and from where I signed, and the fingerprint of the document, is kept with it.")


def seed() -> dict:
    """The rows `install_contract_pack('gb')` keeps — structure, not wording: a customer's templates and clauses
    are their own configuration (a customer's own are set by its own session)."""
    emp = {"pack": PACK, "approval_policy": "standard", "retention_rule": "simple-6y"}
    return {
        "retention_rule": [
            {"rule_code": "simple-6y", "years_after_end": 6, "dead_draft_days": 30,
             "basis": "Limitation Act 1980 s.5 — six years for a simple contract"},
            {"rule_code": "deed-12y", "years_after_end": 12, "dead_draft_days": 30,
             "basis": "Limitation Act 1980 s.8 — twelve years for a deed"}],
        "approval_policy": [
            {"policy_code": "standard", "rule_no": 1, "applies_to": "*", "approver_group": "legal",
             "fallback_group": "director", "timeout_working_days": 2},
            {"policy_code": "standard", "rule_no": 2, "applies_to": "bound", "approver_group": "finance",
             "fallback_group": "director", "timeout_working_days": 2}],
        "reminder_cadence": [
            {"cadence_code": "signer-chase", "kind": "SIGNER_CHASE", "every_days": 3, "max_count": 3,
             "unit": "working_days"},
            {"cadence_code": "key-dates", "kind": "KEY_DATE", "days_before": [120, 90, 60, 30]},
            {"cadence_code": "key-dates-short", "kind": "KEY_DATE", "days_before": [30, 7]},
            {"cadence_code": "statement-due", "kind": "KEY_DATE", "days_before": [14, 7, 1, 0]}],
        "consent_text": [{"version": "v1", "text": CONSENT_V1}],
        "setting": [
            {"key": "link_lifetime_days", "value": "14"}, {"key": "signed_copy_link_days", "value": "30"},
            {"key": "author_may_approve", "value": "false"}, {"key": "consent_text_version", "value": "v1"},
            {"key": "signer_chase_cadence", "value": "signer-chase"},
            {"key": "key_date_cadence", "value": "key-dates-short"},
            {"key": "key_date_cadence:NOTICE_DEADLINE", "value": "key-dates"},
            {"key": "key_date_cadence:PRINCIPAL_STATEMENT_DUE", "value": "statement-due"}],
        "consumer": [
            {"namespace": "hr", "principals": ["sub:hr-system", "role:hr_service"], "owner_group": "hr_admin",
             "label": "hr: starters and leavers"},
            {"namespace": "crm", "principals": ["sub:crm-adapter", "role:crm_service"], "owner_group": "sales",
             "label": "crm's draft_from_quote adapter"}],
        "contract_type": [
            {"type_code": "employment_contract", "label": "employment contract", "allowed_bp_roles": ["employee"],
             "allow_roleless_person": True, "sensitivity": "employment", "pack_checks": ["era_s1"], **emp},
            {"type_code": "freelance_agreement", "label": "freelance services agreement",
             "allowed_bp_roles": ["worker", "supplier"], "allow_roleless_person": True,
             "pack_checks": ["ir35", "ip_assignment"], **emp},
            {"type_code": "nda", "label": "non-disclosure agreement",
             "allowed_bp_roles": ["customer", "supplier", "worker", "employee"], "allow_roleless_person": True, **emp},
            {"type_code": "leaver_letter", "label": "leaver letter", "allowed_bp_roles": ["employee", "worker"],
             "sensitivity": "employment", **emp},
            {"type_code": "ip_assignment", "label": "IP assignment", "allowed_bp_roles": ["worker", "supplier", "employee"],
             "allow_roleless_person": True, "pack_checks": ["ip_assignment"], **emp},
            {"type_code": "deed_of_assignment", "label": "deed of assignment", "allowed_bp_roles": ["supplier", "worker"],
             "execution": "DEED", "esign_allowed": False, "pack": PACK, "approval_policy": "standard",
             "retention_rule": "deed-12y"},
            {"type_code": "customer_order", "label": "customer order", "allowed_bp_roles": ["customer"], **emp},
            {"type_code": "supplier_agreement", "label": "supplier agreement", "allowed_bp_roles": ["supplier"], **emp}],
    }


__all__ = ["PACK", "ERA_S1", "draft_missing", "draft_key_dates", "send_refusals", "status_warnings", "seed",
           "era_variables", "CONSENT_V1"]