dodil/erp-brick-contracts
tests/seed.py
"""The standard paper every test and scenario starts from — written THROUGH THE DOORS (use cases), never as rows:
the gb pack, our legal name and default signatory, a small clause library (Preferred / Alternate / Fallback) and three
approved templates (nda-std, freelance-std, employment-std). Invented wording: a customer's own is its
configuration, kept by its own session.
`post(path, body)` is whatever reaches the contract routes — the brick's own app (paths as is) or a composition (paths
under /contract) — called as contract_admin; `approve(path, body)` as ANOTHER admin (an author may not approve their
own template). Each raises on any refusal.
"""
OUR_NAME = "Acme Studio Ltd"
DIRECTOR = {"full_name": "Dana Director", "email": "director.1@scenario.test", "title": "Director",
"authority_basis": "board resolution of 1 May 2026", "signing_order": 2}
CLAUSES = [
("conf_std", "confidentiality", "PREFERRED", 0, "Confidentiality",
"Each party keeps the other's confidential information confidential, during this agreement and for three years "
"after it ends."),
("liability_std", "liability_cap", "PREFERRED", 0, "Liability",
"Each party's total liability under this agreement is capped at the fees paid in the twelve months before the "
"claim."),
("liability_alt", "liability_cap", "ALTERNATE", 0, "Liability",
"Each party's total liability under this agreement is capped at the fees paid under it."),
("liability_fb", "liability_cap", "FALLBACK", 1, "Liability",
"Each party's total liability under this agreement is capped at twice the fees paid in the twelve months before "
"the claim."),
("services_std", "services", "PREFERRED", 0, "Services",
"{{counterparty_name}} will provide {{services}} from {{start_date}} to {{end_date}} at a rate of {{rate_amount}} "
"per {{rate_basis}}."),
("ip_full", "ip_assignment", "PREFERRED", 0, "Intellectual property",
"You assign to us, with full title guarantee, all copyright and other rights in everything you make for us under "
"this agreement, on creation."),
("ip_on_payment", "ip_assignment", "FALLBACK", 1, "Intellectual property",
"You assign to us all copyright and other rights in everything you make for us under this agreement, on payment "
"of the fees for it."),
("psc_warranty", "psc", "PREFERRED", 0, "Your company",
"You provide the services through {{intermediary_name}}, which warrants that it is responsible for the tax and "
"national insurance due on what it pays you."),
("notice_std", "notice", "PREFERRED", 0, "Ending this agreement",
"Either party may end this agreement by giving {{notice_days}} days' written notice."),
("employment_terms", "employment", "PREFERRED", 0, "Your employment",
"{{our_name}} employs {{legal_name}} as {{job_title}} from {{start_date}} (continuous employment from "
"{{continuous_employment_date}}). Pay: {{rate_amount}} per {{rate_basis}}, paid monthly. Hours: {{hours}}. "
"Holiday: {{holiday_entitlement}}. Place of work: {{place_of_work}}. Duration: {{duration}}. Probation: "
"{{probation}}. Benefits: {{benefits}}. Training: {{training}}. Sick pay: {{sick_pay}}. Other paid leave: "
"{{other_paid_leave}}. Notice: {{notice}}."),
]
MONEY_BAND = {"min": "300", "max": "600", "currency": "GBP"}
def nda_template() -> dict:
return {"template_id": "nda-std", "type_code": "nda", "name": "Mutual NDA",
"slots": [{"slot_no": 1, "family": "confidentiality", "clause_id": "conf_std", "clause_version": 1},
{"slot_no": 2, "family": "liability_cap", "clause_id": "liability_std", "clause_version": 1}],
"variables": [{"name": "counterparty_name", "type": "text", "source": "COUNTERPARTY"},
{"name": "start_date", "type": "date", "source": "DATES", "validation": {"term": "start"}},
{"name": "end_date", "type": "date", "source": "DATES", "required": False,
"validation": {"term": "end"}},
{"name": "notice_days", "type": "int", "source": "CALLER", "required": False,
"validation": {"term": "notice_days"}}]}
def freelance_template(template_id="freelance-std", type_code="freelance_agreement") -> dict:
return {"template_id": template_id, "type_code": type_code, "name": "Freelance services agreement",
"slots": [{"slot_no": 1, "family": "services", "clause_id": "services_std", "clause_version": 1},
{"slot_no": 2, "family": "ip_assignment", "clause_id": "ip_full", "clause_version": 1},
{"slot_no": 3, "family": "liability_cap", "clause_id": "liability_std", "clause_version": 1},
{"slot_no": 4, "family": "psc", "kind": "optional", "clause_id": "psc_warranty",
"clause_version": 1, "condition": {"var": "engagement_via", "eq": "PSC"}},
{"slot_no": 5, "family": "notice", "clause_id": "notice_std", "clause_version": 1}],
"variables": [
{"name": "counterparty_name", "type": "text", "source": "COUNTERPARTY"},
{"name": "services", "type": "text", "source": "ENGAGEMENT",
"default_value": "the creative services each statement of work describes"},
{"name": "start_date", "type": "date", "source": "DATES", "validation": {"term": "start"}},
{"name": "end_date", "type": "date", "source": "DATES", "validation": {"term": "end"}},
{"name": "rate_amount", "type": "rate", "source": "PAY", "validation": MONEY_BAND, "label": "day rate"},
{"name": "currency", "type": "text", "source": "PAY", "default_value": "GBP"},
{"name": "rate_basis", "type": "enum", "source": "PAY", "enum_values": ["DAY", "HOUR", "FIXED"],
"aliases": {"day": "DAY", "hour": "HOUR", "fixed": "FIXED"}, "default_value": "DAY"},
{"name": "notice_days", "type": "int", "source": "CALLER", "default_value": "30",
"validation": {"term": "notice_days"}},
{"name": "engagement_via", "type": "enum", "source": "ENGAGEMENT", "required": False,
"enum_values": ["SOLE_TRADER", "PSC", "UMBRELLA", "AGENCY"]},
{"name": "intermediary_name", "type": "text", "source": "ENGAGEMENT", "required": True,
"condition": {"var": "engagement_via", "eq": "PSC"}},
{"name": "client_size_regime", "type": "enum", "source": "CALLER", "required": False,
"enum_values": ["SMALL", "MEDIUM_LARGE", "PUBLIC"],
"aliases": {"small": "SMALL", "medium": "MEDIUM_LARGE", "large": "MEDIUM_LARGE", "public": "PUBLIC"}},
{"name": "ir35_determination", "type": "enum", "source": "ENGAGEMENT", "required": False,
"enum_values": ["INSIDE", "OUTSIDE", "NOT_CLIENT_DECISION"]},
{"name": "sds_issued_on", "type": "date", "source": "ENGAGEMENT", "required": False},
{"name": "sds_reference", "type": "text", "source": "ENGAGEMENT", "required": False}]}
ERA_DEFAULTS = {"hours": "37.5 hours a week, Monday to Friday", "holiday_entitlement":
"28 days a year including bank holidays", "duration": "permanent", "probation": "three months",
"benefits": "none beyond those the law requires", "training": "none required",
"sick_pay": "statutory sick pay", "other_paid_leave": "as the law provides",
"notice": "one week for each year of service, up to twelve weeks"}
def employment_template(template_id="employment-std", type_code="employment_contract", defaults=None) -> dict:
d = ERA_DEFAULTS if defaults is None else defaults
plain = [{"name": n, "type": "text", "source": "TEMPLATE", **({"default_value": d[n]} if n in d else {})}
for n in ("hours", "holiday_entitlement", "duration", "probation", "benefits", "training", "sick_pay",
"other_paid_leave", "notice")]
return {"template_id": template_id, "type_code": type_code, "name": "Employment contract",
"slots": [{"slot_no": 1, "family": "employment", "clause_id": "employment_terms", "clause_version": 1},
{"slot_no": 2, "family": "confidentiality", "clause_id": "conf_std", "clause_version": 1}],
"variables": [
{"name": "legal_name", "type": "text", "source": "COUNTERPARTY", "validation": {"bp_field": "legal_name"}},
{"name": "job_title", "type": "text", "source": "ENGAGEMENT"},
{"name": "start_date", "type": "date", "source": "DATES", "validation": {"term": "start"}},
{"name": "continuous_employment_date", "type": "date", "source": "DATES",
"validation": {"default_from": "start_date"}},
{"name": "rate_amount", "type": "rate", "source": "PAY", "label": "pay"},
{"name": "currency", "type": "text", "source": "PAY", "default_value": "GBP"},
{"name": "rate_basis", "type": "enum", "source": "PAY", "enum_values": ["YEAR", "MONTH", "DAY", "HOUR"],
"aliases": {"year": "YEAR", "month": "MONTH", "day": "DAY", "hour": "HOUR"}},
{"name": "location_name", "type": "text", "source": "ENGAGEMENT", "required": False},
{"name": "place_of_work", "type": "text", "source": "ENGAGEMENT",
"validation": {"default_from": "location_name"}, "default_value": "our London office"}] + plain}
def seed(post, approve, prefix: str = "") -> None:
def ok(fn, path, body):
status, out = fn(prefix + path, body)
assert 200 <= status < 300, (path, status, out)
return out
ok(post, "/localisation-packs/gb", {})
ok(post, "/settings/setting", {"rows": [{"key": "our_legal_name", "value": OUR_NAME},
{"key": "mail_from", "value": "contracts@acme.example"},
{"key": "default_signer", "value": __import__("json").dumps(DIRECTOR)}]})
for cid, fam, pos, lvl, title, body in CLAUSES:
ok(post, "/clauses", {"clause_id": cid, "family": fam, "position": pos, "deviation_level": lvl,
"title": title, "body": body})
for tmpl in (nda_template(), freelance_template(), employment_template()):
out = ok(post, "/templates", tmpl)
ok(approve, f"/templates/{tmpl['template_id']}/versions/{out['version']}/approve", {})