# Projects: the module source.
#
# PROVENANCE. Written 2026-10-18 for milestone M5 (Project to Cash), pulled forward, from:
# - a customer design signed 2026-10-18 (an agency that sells implementation phases, monthly hosting and
# maintenance, and media campaigns). Its REFERENCE steps are kept here; its own choices are config rows
# and variation points below, never constants;
# - what exists beside it: business-partner (bp_id), contracts (an active contract is what work is planned
# from), sales-order (a confirmed order is the other source), and invoicing (ar v2: raise_for_acceptance,
# AR-29, bills an accepted one-off item);
# - APQC PCF Cross-Industry, category 5.0 Deliver Services: 20058 (5.3 Deliver service to customer), 20059
# (5.3.1 Initiate service delivery), 20069 (5.3.2 Execute service delivery), 20077 (5.3.3 Complete service
# delivery), 20080 (5.3.3.3 Confirm delivery according to contract terms). Ids read from the v7.4 PDF:
# confirm against v8.0 before release.
# SIGNED OFF: v1 by Seemo (Mohamed Rizk), Founder, 2026-10-18, both processes (item 1c9e4407, project afc6da20):
# signoff/projects/, recorded by erp-compose signoff record from this file.
#
# Four decisions:
# - work is planned from what was SOLD, and the seller sends the sold lines in the call: this module never
# reads a contract's, an order's or a quote's tables
# - only work somebody delivers becomes an item: a monthly or usage line elapses, it is not delivered, and is
# billed by invoicing's schedules, never here
# - "done" needs evidence, and acceptance is how it is recorded (by the client, or deemed after a warning),
# always saying which
# - this module never writes an invoice: an accepted item asks invoicing for one (AR-29)
id: projects
name: Projects & service delivery
short: Projects
version: 1
maturity: planned
summary: >-
Turn what you sold into a delivery plan, see what is late and what is waiting on the client, get each piece
of work accepted with proof, and know at the end what was delivered against what was sold, so that billing
follows delivery instead of a spreadsheet.
facets:
domain: operations
industries: [any]
customer_words:
- "what we sold becomes a delivery plan"
- "we're waiting on the client's brief"
- "the client never signs off, so the invoice never goes"
- "did we deliver everything we sold"
- "makegood"
- "what's due this week across every client"
replaces: [a project spreadsheet per client, Trello, Asana, Monday, Odoo Project]
pillars: [sql]
first_module_fit: medium
depends_on:
- module: business-partner
why: a project is for a customer, and every module must mean the same one
required: true
- module: contracts
why: an active contract is the commonest source of planned work (PJ-20)
required: recommended
- module: sales-order
why: a confirmed order is the other source of planned work (PJ-21)
required: optional
- module: ar
why: an accepted one-off item is billed on acceptance (PJ-22, AR-29)
required: recommended
- module: crm
why: internal to-dos on an item go through the activity brick (PJ-24)
required: optional
feeds: [ar, contracts]
owns_master_data: []
references_master_data: [business_partner, product]
actors:
- id: project_lead
name: Project lead / account manager
does: owns a project, plans it from what was sold, records the client's answer, raises makegoods
sees: their own projects; a manager sees every project
permissions: [pj:project:read, pj:project:write, pj:item:read, pj:item:write, pj:item:accept]
- id: contributor
name: Whoever does the work
does: starts, blocks and submits the items assigned to them, with evidence
sees: the items assigned to them and their project's headline
permissions: [pj:item:read, pj:item:work]
- id: client_contact
name: The client's contact
external: true
does: supplies briefs and assets, reviews what was delivered, follows status
sees: their own project only, through a link, read-only
permissions: []
- id: finance
name: Finance / the biller
does: reads what is billable and the makegood liability
sees: every project, read-only
permissions: [pj:project:read, pj:item:read]
- id: manager
name: Operations manager / the owner
does: sets the review window, warning notice, state names, block reasons and evidence kinds
sees: every project
permissions: [pj:project:read, pj:item:read, pj:settings:write]
- id: system
name: The system
does: plans projects from what was sold, shows the review clock, reconciles a closed project window
sees: everything, through its own service account
permissions: [pj:project:read, pj:project:write, pj:item:read]
stories:
# ── person ────────────────────────────────────────────────────────────────────────────────
- id: PJ-01
kind: person
actor: project_lead
story: As a project lead, I want the work planned from what was sold, so that every item carries its money from day one.
given: "an active contract for bp_id 7311 selling 2 x implementation phase (one-off, single payment), 12 x hosting month (recurring) and 5 x maintenance month (recurring)"
when: the project is planned
then: "one project and 2 items, one per implementation phase, each carrying its sold line id; hosting and maintenance make no items (they elapse and are billed by schedule); planning again creates nothing"
screen: Project
path: { process: projects.item, choices: {} }
reference: not built
customer_varies: which kinds of line become items, and whether a single-payment line expands per unit (PJ-Q1)
- id: PJ-02
kind: person
actor: contributor
story: As the person doing the work, I want to say I am waiting on the client, so that their delay is not counted as ours.
given: an item in progress that needs the client's brief
when: I block it with the reason "waiting for brief"
then: "it shows blocked on the client since that date; unblocking adds the blocked days to the item, and slippage excludes them; blocking without a reason is refused (\"say what you are waiting for\")"
screen: Item
reference: not built
- id: PJ-03
kind: person
actor: project_lead
story: As a project lead, I want work in review to get an answer or a deadline, so that it does not sit in review forever.
given: "an item submitted on 2026-11-02, a review window of 10 working days and a warning notice of 5 working days"
when: the window passes with no answer, I send the warning, and the notice passes too
then: "I may record the item accepted as DEEMED, recorded as deemed with the warning's date; recording deemed acceptance before the warning was sent, or before its notice ends, is refused"
screen: Item
reference: not built
customer_varies: the review window, the warning notice, and whether deemed acceptance exists at all
- id: PJ-04
kind: person
actor: project_lead
story: As a project lead, I want "done" to need proof, so that delivered means evidenced.
given: an item in review with no evidence attached
when: I record it accepted
then: 'refused: "Attach the evidence first: a link, a file or a sign-off"; with a link attached it is done, with who accepted it, when, and how (recorded or deemed)'
screen: Item
reference: not built
- id: PJ-05
kind: person
actor: client_contact
story: As the client's contact, I want to see where my project is without asking, so that I am not chasing.
given: a link issued for project A
when: I open it without signing in
then: "I see project A's items, their states and their evidence, read-only; the same link cannot reach project B (the same not-found answer as a dead link)"
screen: Client status
reference: not built
- id: PJ-06
kind: person
actor: finance
story: As finance, I want to see sold against delivered per line, so that I bill what was accepted and nothing else.
given: a project whose window has closed
when: I open its reconciliation
then: "per sold line: sold, delivered (accepted), short, over; only accepted items count as delivered; the figures are the stored reconciliation, not a recount"
screen: Reconciliation
reference: not built
- id: PJ-07
kind: person
actor: project_lead
story: As a project lead, I want a shortfall to become visible extra work with no revenue, so that we make good without billing twice.
given: "a reconciled project: 6 sold, 5 delivered on one line"
when: I raise a makegood for the short item
then: "a new item with source makegood, no sold line id, pointing at the short item; the short item stays short; raising a second makegood for the same short item is refused"
screen: Reconciliation
reference: not built
- id: PJ-08
kind: person
actor: contributor
story: As anyone delivering, I want what is due this week across every client, overdue first, so that nothing slips unseen.
given: items across five projects, two overdue, one blocked
when: I open the board
then: "overdue first, then due today, then this week, in my time zone; blocked items show who they wait on; I see only what I may see"
screen: Board
reference: not built
- id: PJ-09
kind: person
actor: project_lead
story: As a project lead, I want to request changes and keep the same item, so that a revision is not mistaken for new work.
given: an item in review
when: I record the client's change request with the reason
then: "the same item returns to in progress, its revision count goes up, and the review clock stops; recording changes without a reason is refused"
screen: Item
reference: not built
- id: PJ-10
kind: person
actor: project_lead
story: As a project lead, I want to descope an item with a reason, so that the plan matches what we now owe.
given: an item not yet done
when: I descope it with the reason "replaced by change order CO-3"
then: "it is cancelled, keeps its history, and reconciliation counts it as descoped, not short; a done item cannot be descoped"
screen: Item
reference: not built
# ── system ────────────────────────────────────────────────────────────────────────────────
- id: PJ-11
kind: system
actor: system
trigger: on read (derived)
story: The system shows each item's review clock, so that a person knows when to warn the client.
given: an item in review for 8 of 10 working days
when: anyone opens it or the board
then: "it shows 2 working days left, then the warning due, then deemed acceptance possible on a date; nothing is stored and NOTHING is sent: a person sends the warning"
reference: not built
- id: PJ-12
kind: system
actor: system
trigger: daily, for projects whose window ended
story: The system reconciles a project when its window closes, so that a shortfall is known while it can still be made good.
given: a project whose end date passed yesterday
when: the daily pass runs
then: "per sold line it stores sold, delivered, short, over and descoped, and closes the project; running it twice reconciles once (keyed on the project)"
reference: not built
- id: PJ-13
kind: system
actor: system
trigger: when a project is planned and the step is on
story: The system drafts the plan's dates and owners from the sold lines and similar past projects, so that the lead checks instead of types.
given: a newly planned project and three finished projects for similar products
when: it is planned
then: "a suggested due date and owner per item, with the past projects it learned from; nothing changes until the lead accepts the suggestion"
reference: not built
# ── integration ───────────────────────────────────────────────────────────────────────────
- id: PJ-20
kind: integration
actor: system
trigger: when a contract becomes active
story: An active contract's sold lines become a project, without this module reading the contract.
given: the contract's composition adapter sends plan_from_sale(source_kind contract, source_id, bp_id, starts_on, ends_on, lines[])
when: the call arrives
then: "one project keyed pj:contract:<source_id>, items per the expansion rule; a second call returns the same project and creates no item; a failed plan never undoes the contract"
owner: projects
reference: not built
- id: PJ-21
kind: integration
actor: system
trigger: when a sales order is confirmed with lines to deliver
story: A confirmed order's deliverable lines become a project, the same way.
given: a confirmed order whose lines carry billing kind and plan
when: the order's adapter calls plan_from_sale(source_kind sales_order, ...)
then: "one project keyed pj:sales_order:<sales_order_id>; accepted items report delivered quantities back through the order's record_delivery route (SO-23)"
owner: projects
reference: not built
- id: PJ-22
kind: integration
actor: system
trigger: when an item is accepted and its line is billed on acceptance
story: An accepted one-off item asks invoicing for its draft, so that the invoice follows the proof.
given: an accepted item from a one-off, single-payment line worth 35,000.00
when: it is accepted (recorded or deemed)
then: "invoicing's raise_for_acceptance(item) is called, keyed acc:<item_id> (AR-29); accepting is never undone by invoicing being down: the item shows billing pending and a retry bills once"
owner: ar
reference: not built
- id: PJ-23
kind: integration
actor: system
trigger: when a contract's phase gate is checked
story: The contract reads acceptance from here rather than keeping a second copy.
given: a contract whose next phase may start only when the previous one is accepted
when: the contract asks
then: "this module's read route answers per sold line: accepted or not, how, and when"
owner: projects
reference: not built
- id: PJ-24
kind: integration
actor: contributor
trigger: when someone adds a to-do to an item
story: Internal to-dos hang off an item through the activity brick, so that there is one task list.
given: an item
when: a to-do is added from it
then: "the activity brick records it related to the item; this module stores nothing of it"
owner: crm
reference: not built
processes:
- key: item
pcf_id: 20069
pcf_name: Execute service delivery
reference_names: ["Project to Cash", "Deliver service to customer"]
brick: projects
- key: project
pcf_id: 20077
pcf_name: Complete service delivery
reference_names: ["Project to Cash", "Confirm delivery according to contract terms"]
brick: projects
workflows:
- entity: item
states: [planned, in_progress, blocked, in_review, done, cancelled]
terminal: [done, cancelled]
transitions:
- { key: plan_from_contract, name: Plan items from what was sold, from: none, to: planned, by: system, gate: "a sale arrived (an active contract or a confirmed order); only lines that are delivered make items", effect: "keyed project|line|n, so planning again creates nothing" }
- { key: raise_makegood, name: Raise a makegood (no revenue), from: none, to: planned, by: project_lead, gate: "the project was reconciled short", effect: "source makegood, no sold line; the short item stays short" }
- { key: ai_draft_plan, name: "AI suggests dates and owners (suggest)", from: planned, to: planned, by: system, gate: "the step is on", effect: "suggestions only, until the lead accepts them" }
- { key: start, name: Do the work, from: planned, to: in_progress, by: contributor }
- { key: block, name: "Block, with the reason", from: in_progress, to: blocked, by: contributor, requires: reason, effect: "stamps blocked_since; blocked days are not our slippage" }
- { key: ai_chase_brief, name: "AI drafts the reminder for the brief (suggest)", from: blocked, to: blocked, by: system, gate: "blocked on the client past the chase days and the step is on", effect: "a draft message a person may send" }
- { key: provide_brief, name: Send the brief or assets, from: blocked, to: blocked, by: client_contact }
- { key: unblock, name: Unblock when the brief arrives, from: blocked, to: in_progress, by: contributor, effect: "accrues blocked days" }
- { key: submit, name: "Submit for review, with evidence", from: in_progress, to: in_review, by: contributor, effect: "starts the review clock" }
- { key: review, name: Review the delivery, from: in_review, to: in_review, by: client_contact, effect: "accepted, changes, or no answer" }
- { key: send_warning, name: Send the deemed-acceptance warning, from: in_review, to: in_review, by: project_lead, gate: "no answer within the review window", effect: "a person sends it; the system only shows the clock" }
- { key: request_changes, name: Record changes requested, from: in_review, to: in_progress, by: project_lead, requires: reason, effect: "the same item, revision +1, never a new one" }
- { key: accept, name: Record acceptance, from: in_review, to: done, by: project_lead, gate: "evidence attached, else \"Attach the evidence first: a link, a file or a sign-off\"", effect: "approved_how recorded; a line billed on acceptance asks invoicing for its draft (AR-29)" }
- { key: accept_deemed, name: Record deemed acceptance, from: in_review, to: done, by: project_lead, gate: "the warning was sent and its notice has ended; evidence attached", effect: "approved_how deemed; billed as for accept", timer: "the warning notice after the warning was sent" }
- { key: descope, name: Descope, from: [planned, in_progress, blocked, in_review], to: cancelled, by: project_lead, requires: reason, effect: "counted as descoped, not short" }
note: >-
"Short" is DERIVED: an item is short when its project's window has closed and it is not done or descoped.
Approval and delivery are timestamps on the item, not states. The state NAMES are rows the customer may
rename (pj_state); the transitions are code.
- entity: project
states: [planned, active, closed, cancelled]
terminal: [closed, cancelled]
transitions:
- { key: plan_project, name: Open the project for the sale, from: none, to: planned, by: system, gate: "a contract became active or an order was confirmed (PJ-20, PJ-21)", effect: "keyed pj:<source_kind>:<source_id>" }
- { key: activate, name: Start the project, from: planned, to: active, by: project_lead, gate: "the first item started" }
- { key: reconcile, name: Reconcile sold vs delivered, from: active, to: closed, by: system, effect: "stores sold, delivered, short, over and descoped per sold line", timer: "the project window ends (ends_on)" }
- { key: ai_flag_shortfall, name: "AI flags a likely shortfall (suggest)", from: active, to: active, by: system, gate: "the window is closing and the step is on", effect: "a warning with the items at risk, while they can still be delivered" }
- { key: cancel_project, name: Cancel the project, from: [planned, active], to: cancelled, by: project_lead, requires: reason }
note: >-
Reconciliation is a stored RESULT on the closed project, not a state. A project with no deliverable lines
is never opened: a sale of only monthly or usage lines plans nothing.
entities:
- id: project
meaning: one piece of delivery for one customer from one sale (an implementation, a campaign, a retainer's deliverables)
natural_key: 'project_id: VARCHAR, derived from the sale: "pj:<source_kind>:<source_id>"; a project by hand is "pj:manual:<uuid>"'
key_attributes: [bp_id, source_kind, source_id, quote_id (nullable; a project may be fed by several), owner, starts_on, ends_on, state, reconciled_at, reconciliation_json, version]
relationships: ["-> business_partner", "1..n item"]
master_data: module-owned
reference_table: pj_project
- id: item
meaning: one committed thing somebody delivers
natural_key: '"<project_id>|<line_id>|<n>"; a makegood "<project_id>|mg|<short item_id>"'
key_attributes: [project_id, bp_id, source_kind, source_id, line_id (nullable for a makegood), product_id, billing_kind, billing_plan, source (sold | makegood | change_order | goodwill), title, owner, due_on, state, blocked_reason, blocked_since, blocked_days, submitted_at, revision, warned_at, approved_at, approved_by, approved_how (recorded | deemed), delivered_at, evidence, evidence_kind, makegood_for, billing_status (not_billed | pending | requested), version]
relationships: ["-> project", "0..1 makegood_for item"]
master_data: module-owned
reference_table: pj_item
note: "the sale's lineage (bp_id, source, line) is copied onto the item so any rollup is one GROUP BY and an item still reconciles if its project row is unreadable"
- id: state
meaning: the state names the customer sees, and which count as open, terminal and delivered
natural_key: state_id
key_attributes: [name, position, is_open, is_terminal, counts_as_delivered]
master_data: module-owned (configuration)
reference_table: pj_state
- id: event
meaning: append-only history of every move, with who and why
natural_key: '"evt:<item_id>:<ts>"'
key_attributes: [item_id, kind, from_state, to_state, actor, detail]
master_data: module-owned
reference_table: pj_event
- id: settings
meaning: how this business delivers
natural_key: key
key_attributes: [value]
master_data: module-owned (configuration)
reference_table: pj_setting
master_data:
owns: [pj_project, pj_item, pj_state, pj_event, pj_setting]
references:
- entity: business_partner
owner: business-partner
how: pj_project.bp_id = business_partner.bp_id
if_absent: "projects keep their own client names: the duplication the party master exists to prevent"
- entity: product
owner: product
how: pj_item.product_id, billing kind and plan copied at planning time
if_absent: "the sold lines still carry billing kind and plan in the call; the product name is free text"
warning: >-
A delivery tool that keeps its own list of what was sold drifts from the contract the day a line changes.
The sale is sent in; the item keeps the line's id; reconciliation is per sold line.
jobs:
- story: PJ-11
trigger: on read
runs: the review clock
where_options: [derived on read]
idempotent: "nothing stored"
- story: PJ-12
trigger: daily
runs: reconcile projects whose window ended
where_options: [external scheduler calling reconcile_due, always-on worker (--reserved 1 --max-replicas 1)]
idempotent: "keyed on project_id; a reconciled project is skipped"
- story: PJ-13
trigger: when a project is planned
runs: the plan suggestion
where_options: [in the same request, external scheduler]
idempotent: "one suggestion per project version; re-running replaces an unaccepted suggestion"
seams:
- story: PJ-20
with: contracts
owner: projects
rule: "an adapter in the composition calls plan_from_sale(source_kind contract, ...) AFTER the contract's commit; this module never reads contract_ tables"
- story: PJ-21
with: sales-order
owner: projects
rule: "the order's adapter calls plan_from_sale(source_kind sales_order, ...); accepted quantities go back through sales-order's record_delivery (SO-23); this module never writes so_ tables"
- story: PJ-22
with: ar
owner: ar
rule: "this module calls invoicing's raise_for_acceptance(item), keyed acc:<item_id>; it never writes ar_ tables; invoicing down leaves billing_status pending for a retry"
- story: PJ-23
with: contracts
owner: projects
rule: "the contract calls this module's acceptance read route; contract expiry never reads delivery"
- story: PJ-24
with: crm
owner: crm
rule: "to-dos through the activity brick's route, related to the item; this module stores none"
screens:
- { id: board, name: Board, stories: [PJ-02, PJ-08, PJ-11], actors: [project_lead, contributor] }
- { id: project, name: Project, stories: [PJ-01, PJ-03, PJ-13], actors: [project_lead] }
- { id: item, name: Item, stories: [PJ-02, PJ-03, PJ-04, PJ-09, PJ-10], actors: [project_lead, contributor] }
- { id: client_status, name: Client status, stories: [PJ-05], actors: [client_contact],
note: "public, through the composition's public app; a link per project, read-only" }
- { id: reconciliation, name: Reconciliation, stories: [PJ-06, PJ-07, PJ-12], actors: [finance, project_lead] }
- { id: settings, name: Settings, stories: [], actors: [manager],
note: "every config_row below: expansion rule, review window, warning notice, deemed acceptance on/off, state names, block reasons, evidence kinds, chase days, AI step modes" }
permissions:
namespace: pj
catalog:
- pj:project:read
- pj:project:write
- pj:item:read
- pj:item:work # start, block, unblock, submit: one's own items
- pj:item:write # assign, re-date, descope
- pj:item:accept # record acceptance, deemed acceptance, changes
- pj:settings:write
row_visibility:
rule: "a lead sees their own projects, a manager and finance every project; a contributor sees the items assigned to them; a client sees one project through its link"
enforced: "in the use case"
not_found_not_forbidden: true
config_rows:
- { table: pj_setting, changes: "which billing kinds and plans make items, and whether a single-payment line expands one item per unit; the review window and warning notice (working days); whether deemed acceptance exists; the chase days for a blocked item; the block reasons and evidence kinds offered; each AI step's mode" }
- { table: pj_state, changes: "the names of the states and their order (e.g. 'In review' called 'With client')" }
in_code_not_rows:
- "that an item is planned once per sold line and unit: a second plan of the same sale creates nothing"
- "that acceptance needs evidence, and records how it was given (recorded or deemed)"
- "that deemed acceptance needs a warning that was sent and a notice that ended"
- "that the system never sends a client anything: it shows the clock and drafts, a person sends"
- "that a makegood carries no sold line and never bills"
- "that this module asks invoicing and never writes an invoice"
ai_steps:
- key: ai_draft_plan
benefit: "a plan of twenty items gets dates and owners in seconds, from how similar work actually went"
mode: suggest
modes_allowed: ["off", suggest]
safe_for_auto: false # a date promised to a client is a commitment
inputs: [the sold lines, the project window, finished projects for the same products with their actual durations and owners]
output: "a due date and an owner per item, with the past projects each suggestion came from"
fallback: "items are dated at the project's end and owned by the lead"
eval: "finished projects replayed: suggested durations against actual, owner match rate"
- key: ai_chase_brief
benefit: "a brief that is late gets a polite, specific reminder drafted the day it is due, not a week later"
mode: suggest
modes_allowed: ["off", suggest]
safe_for_auto: false # it speaks to the client
inputs: [the blocked item, its reason, the days blocked, earlier messages on the item]
output: "a draft message naming what is missing and since when, for a person to edit and send"
fallback: "the board shows the item as blocked past the chase days"
eval: "drafts rated by leads for accuracy (names the right asset and date) and tone"
- key: ai_flag_shortfall
benefit: "a shortfall is seen while the window is open and the work can still be done"
mode: suggest
modes_allowed: ["off", suggest]
safe_for_auto: true # it only warns
inputs: [open items, their due dates and blocked days, the window's end, past velocity on the project]
output: "the items at risk of not being done by the window's end, with why"
fallback: "reconciliation reports the shortfall when the window closes"
eval: "closed projects replayed two weeks before their end: precision and recall of the flagged items against the actual shortfall"
tests:
acceptance: "one per story id above, named for it"
access:
- "a contributor sees and moves only their own items; another project's item is a 404"
- "finance reads every project and changes nothing"
- "a client link reaches one project, read-only; any other id answers like a dead link"
- "only pj:item:accept records acceptance, deemed acceptance or changes"
invariants:
- "the same sale (source_kind, source_id) yields one project, and the same line and unit one item"
- "no item is done without evidence; every done item says recorded or deemed"
- "deemed acceptance never precedes warned_at plus the notice"
- "a makegood has no line_id and is never billed"
- "an accepted item asks invoicing once (acc:<item_id>), however many times acceptance is retried"
- "reconciliation per line: sold = delivered + short + descoped - over"
- "short is never stored on an item"
scenarios:
- id: SC-PJ-01
name: a mixed sale plans only what is delivered
stories: [PJ-01, PJ-20]
given: "an active contract: 2 x implementation phase (one-off, single), 12 x hosting month (recurring), 5 x maintenance month (recurring)"
when: plan_from_sale is called twice
then: "one project and 2 items; the second call creates nothing"
- id: SC-PJ-02
name: one implementation paid in instalments is one item
stories: [PJ-01]
given: "1 x implementation sold on 6 instalments (one-off, periodic) with the expansion setting 'periodic: one item'"
when: it is planned and accepted
then: "one item; acceptance bills nothing here, because the instalments are invoicing's schedule"
- id: SC-PJ-03
name: done without proof is refused
stories: [PJ-04]
given: an item in review with no evidence
when: the lead records acceptance
then: '"Attach the evidence first: a link, a file or a sign-off"'
- id: SC-PJ-04
name: no answer, warned, deemed, billed once
stories: [PJ-03, PJ-11, PJ-22]
given: "an item from a one-off, single-payment line worth 35,000.00, a 10-day window and a 5-day notice"
when: "the window passes, the lead sends the warning, the notice passes, deemed acceptance is recorded twice"
then: "done, approved_how deemed; one draft invoice keyed acc:<item_id>"
- id: SC-PJ-05
name: waiting on the client is not our slippage
stories: [PJ-02]
given: an item due in 10 days
when: it is blocked for 4 days waiting for the brief, then unblocked
then: "blocked_days 4; slippage measured without them"
- id: SC-PJ-06
name: a short window raises one makegood
stories: [PJ-06, PJ-07, PJ-12]
given: "6 sold, 5 accepted when the window closes"
when: the daily pass runs twice, then the lead raises a makegood twice
then: "one reconciliation (short 1); one makegood with no sold line; the second is refused"
- id: SC-PJ-07
name: the client link sees one project
stories: [PJ-05]
given: links for projects A and B
when: A's link asks for B
then: the same answer as a dead link
scale:
reads: "the tables reader serves 16 concurrent reads; the board reads items across projects: one query per page, filtered by owner, with the lineage already on the item"
hot_paths: ["the board", "reconcile_due over many closed projects"]
workers: none
volumes: "thousands of items a month is comfortable; page the board and reconcile in batches"
customer_varies:
- question: Which of the things you sell are delivered as work, and which simply run (a monthly service)?
changes: "pj_setting expansion rule: billing kinds and plans that make items"
typical:
agencies_and_consultancies: one-off phases and deliverables are work; hosting and retainers run
media_sellers: each placement or insertion is an item
- question: When you sell 3 of the same deliverable on one payment, is that 3 pieces of work or one?
changes: "pj_setting: expand a single-payment line per unit"
typical:
implementation_phases: one item per phase
instalment_billing: one item, paid in parts
- question: How long does a client have to review, and do you treat silence as acceptance after a warning?
changes: "pj_setting review window, warning notice, deemed acceptance on/off"
typical:
software_implementation: 10 working days to review, deemed accepted 5 working days after a written warning
creative_work: a fixed number of revision rounds, no deemed acceptance
warning: "deemed acceptance must be written into the contract the client signed, or it bills work the client can dispute"
- question: What counts as proof that something was delivered?
changes: "pj_setting evidence kinds"
typical:
software: a link to the live feature or a signed acceptance form
media: a proof of publication or a screenshot of the placement
- question: Should the client see their project's status, and can they approve from it?
changes: "the client status screen; in this version the link is read-only and staff record acceptance"
variation_points:
- node: Task_plan_from_contract
op: row_value
default: "one-off lines make items; a single-payment line expands per unit; a periodic one is one item"
depth: 1
guideline: "warn - an item per month of a recurring service turns billing into busywork; invoicing schedules bill it"
- node: Task_accept_deemed
op: on_off
default: "on, after a warning and its notice"
depth: 1
guideline: "warn - without it, a silent client holds the invoice for one-off work indefinitely; with it, the contract must say so"
- node: Task_send_warning
op: re_time
default: "after the review window (10 working days); notice 5 working days"
depth: 1
guideline: "warn - a window shorter than the contract's acceptance clause is unenforceable"
- node: Task_review
op: re_lane
default: "the client reviews, staff record the answer"
depth: 2
guideline: "warn - letting the client accept from the link needs an identity on the public door; staff recording is the reference"
- node: Task_accept
op: row_value
default: "evidence required"
depth: 1
requirement: "refuse - acceptance without evidence is how delivered stops meaning delivered; the evidence kinds are configurable, the rule is not"
- node: Task_ai_draft_plan
op: on_off
default: "suggest"
depth: 1
guideline: "warn - off is safe; auto is not offered, because a date promised to a client is a commitment"
- node: Task_ai_chase_brief
op: on_off
default: "suggest"
depth: 1
guideline: "warn - auto is not offered: the system never writes to a client on its own"
- node: Task_ai_flag_shortfall
op: on_off
default: "suggest"
depth: 1
guideline: "warn - off means the shortfall is found at reconciliation, after the window"
reference:
repos: [] # erp-brick-projects is built and audited before it is published
packages: []
contracts: []
build_guide: null
known_gaps:
- "no time tracking or timesheets: effort is not recorded, only delivery"
- "no resource capacity planning across projects (PCF 5.2): owners are assigned, not balanced"
- "no Gantt, dependencies or critical path: items have due dates, not predecessors"
- "commitments over time (\"12 articles across the year\") are not modelled; they arrive as items per unit"
- "the client link is read-only: acceptance is recorded by staff"
- "no change-order pricing: a change order is an item with source change_order, priced and billed by the seller"
- "the AI-step marker in the BPMN is a task name until erp-platform draws dodil:ai / dodil:mode (M0 AI-step contract)"