Parish LarderWhere Local Happens
Find a Handyman guides

Find a Handyman · Platform admins

Find a Handyman — admin / moderation overview

What the platform team handles around the jobs board — module gating per community, dispute mediation, fee maths and the audit trail.

Enabling the module per community

Find a Handyman is in DEFAULT_MODULES_BY_KIND for parishes, villages, towns and "other" community kinds — they pick it up automatically on creation. Other kinds (clubs, PTAs, churches) don't default-enable it because it's a poor fit for those groups.

Community admins can toggle the module on or off from the admin dashboard → Modules. The tile disappears from welcome surfaces when it's off; existing job postings stay in the database for audit but can't be browsed.

Tradesperson onboarding

Tradespeople sign up as merchants like any seller. The Business mode column on the merchants row drives which mobile-app tab bar they see; 'handyman' hides Orders / Products / Scan and shows Jobs / Diary / Communities instead.

To respond to jobs in a community, a trade must (a) be a member of the community and (b) have a public directory listing with one of the trade categories. Existing /merchants admin tools cover both.

Engagement state machine

An engagement is a row in job_engagements with a status enum: pending_payment → escrowed → in_progress → completion_proposed → released (or disputed / refunded).

Plus the schedule_status (unscheduled → pending_customer_ok → confirmed) which gates the visits table. start-work / finish-work on individual visits records GPS + timestamp into the job_visits row.

Reschedule proposals live as proposed_scheduled_at + proposed_by_user_id on the visit row. Either party proposes; the other party must accept to move the scheduled_at field. Server enforces that the proposer can't decide their own proposal.

Fee maths + reconciliation

Customer is charged quote + 10% surcharge via Stripe Checkout. Funds land in the platform's Stripe balance (escrow).

On accept-completion we Stripe Transfer the trade's payout (quote − 10% platform fee) to their Connect account. Platform retains the 10% surcharge + the 10% fee — about 18% of what the customer paid.

All four pence values (quote, customer surcharge, total charged, platform fee, tradesperson payout) snapshot onto the job_engagements row at create-time so quote edits later can't retroactively change the deal. Reconciliation queries hang off those snapshots, not the live quote.

Disputes

If a customer raises a dispute (reason ≥ 20 chars), engagement.status flips to disputed and funds stay in escrow. Email lands in the admin inbox.

Admin mediation options today: full refund to customer (engagement → refunded), full release to trade (engagement → released), or split (manual Stripe Transfer + manual refund — code path will land in a follow-up). The audit log captures who decided what + when.

GPS audit trail

Each job_visits row carries work_started_at + lat/lng and work_finished_at + lat/lng. The fields are nullable — if the trade declines location permission we still record the timestamp. Plot start/finish on a map to verify on-site presence in disputed jobs.

Old engagements created before the visit-schedule migration carry engagement-level work_* fields instead. UI falls back to those when job_visits is empty for an engagement.

Find a Handyman — admin / moderation overview — Parish Larder | Parish Larder