Artisans¶
The Artisans module is your roster of external service providers: plumbers, electricians, carpenters, painters, pest-control crews, lift technicians. Track who's reliable, who's available, what they've worked on, how they're rated — and the license + SOP + training + violation governance that gates whether they're allowed to take work in the first place.
Artisans are global, not facility-scoped. An artisan registered at one facility can be assigned to jobs at any other facility that grants them access. Their license, ratings, and reputation roll up across every facility they serve. Suspensions (license-based or violation-threshold-based) apply globally — if one facility's threshold suspends them, every facility sees them suspended until the underlying issue is resolved.
What residents see¶
Residents typically don't browse the artisan list directly. The relevant data surfaces:
- On a maintenance ticket: residents see "Assigned to: [Artisan Name + photo + phone]" once management assigns the work.
- On the rate the work prompt after a ticket is closed: residents see who they're rating across five metrics.
What facility admins / staff do¶
Add an artisan¶
Artisans → + Add Artisan:
- Name + photo (helps the resident identify the technician at the door).
- Trade(s) — multi-select from Plumber / Electrician / Carpenter / AC Technician / Pest Control / Painter / Mason / Welder / General etc.
- Phone (required) + phone alt + email.
- Company (or sole trader) — optional.
- TIN / business registration — for tax.
- ID verification — Ghana Card / Driver Licence / Passport; captured then verified via the admin Verify ID action.
- License Number — auto-generated as
ART-DDMMYYYY-RAND8if left blank (e.g.ART-21052026-A3K9X7P2). Mgmt can override with an externally-issued artisan license number (GASHA / ECCAR / trade-association code). - License Expiry — required. Default: 1 year from today. Drives the auto-suspension job.
- Hourly rate / call-out fee / rate per hour thereafter.
License lifecycle¶
Every artisan has a captured license number and expiry. A daily
background job (runArtisanLicenseSuspensionJob) scans for artisans
whose licenseExpiry has passed and flips them to suspended
state — globally, across every facility. The same flip happens at
the instant the expiry timestamp is reached, so there's no race
window where an expired artisan can still be assigned work.
A suspended artisan:
- Still has a row in the directory + vendor portal access (so they can renew without losing access entirely).
- Is BLOCKED from the maintenance assignment dropdown — the assign
attempt returns
ARTISAN_LICENSE_EXPIRED. - Sees a red banner across all panels of their vendor portal with renewal instructions.
To renew:
- Artisan obtains a renewed license offline (GASHA registration, trade-association renewal, etc.).
- Mgmt updates the license expiry date in the admin form.
- If the facility has the renewal gate enabled, the save is also
blocked unless the artisan has accepted the latest SOPs AND
passed the training quiz since their previous expiry. The save
returns
RENEWAL_REQUIRES_SOP_ACCEPTANCE/RENEWAL_REQUIRES_TRAININGuntil both are done. - Once the renewal goes through, the suspension auto-clears in the same save and the artisan can take work again.
Artisan SOPs¶
Each facility can author and publish an Artisan SOP that artisans must read and accept at first login + at every license renewal. Settings → Artisan SOPs & Training Questions (visible when the Artisan SOP gate is enabled for your facility). The editor mirrors the agent side: Markdown body + JSON training-questions array, with separate Save Draft and Publish actions. Once published:
- New artisans see a blocking modal at first login until they accept.
- Existing artisans see a banner with a sign affordance.
- License renewal is blocked until they've accepted the LATEST version (the SHA-256 of the markdown they signed is stored on the artisan row; a re-publish counts as a new version).
- Once accepted, the artisan gets a 📥 Download signed copy (PDF) affordance — a streamed receipt with the SOP body, typed or drawn signature, signing date + time, acceptance IP, content hash, and the Act 772 attestation paragraph (Ghana's Electronic Transactions Act, 2008).
Signature options at acceptance: type your full legal name OR draw your signature on the freehand canvas — mutually exclusive.
Training quiz¶
Settings → Artisan SOPs & Training Questions also holds the
multiple-choice quiz the artisan takes in their vendor portal under
the 📜 Training tab. Each question carries a prompt + 2–8
options + a correctIndex. Server scores in real time:
- ✓ PASSED — score ≥ facility threshold (default 80%). The
last-pass timestamp (
lastTrainingPassedAt) is stamped; the artisan is cleared for renewal until their next expiry cycle. - ✗ Did not pass — try again. Unlimited re-takes; only the most recent pass counts.
If the artisan's last pass is BEFORE their current license expiry,
the next renewal save is blocked with RENEWAL_REQUIRES_TRAINING.
Violation tracking + auto-suspension¶
When work goes wrong, mgmt files a violation against the artisan (Maintenance ticket → File violation, or Artisans → row → File violation). Each violation has:
- Severity — low / medium / high / critical.
- Description — what happened.
- Status — open (newly filed) → upheld / dismissed / disputed (the artisan opened a dispute) → resolved.
Per-facility thresholds (Settings → Artisans → Auto-suspension thresholds) decide when accumulated violations trigger an automatic suspension:
- Severity floor — the lowest severity that counts (
critical/high/never). - Count — how many upheld violations at-or-above the floor trigger suspension.
A daily background job evaluates the threshold; an immediate re-evaluation also fires when a violation is dismissed (so a successful dispute can clear a suspension without waiting for the next nightly run).
Audit codes:
ARTISAN_AUTO_SUSPENDED_VIOLATION_THRESHOLD /
ARTISAN_AUTO_REACTIVATED_VIOLATION_THRESHOLD_DROPPED.
Like license-based suspensions, violation-based suspensions are global. The threshold that triggers them is per-facility, but the suspension applies everywhere.
Vendor portal¶
Once registered, an artisan gets a vendor-portal login (issued on Grant portal access from the artisan row). They get:
- A read/write view of jobs assigned to them.
- Status updates, completion photos, parts requests.
- A running ledger of work done + amounts owed.
- SOP acceptance + training quiz under the 📜 SOPs and 📜 Training tabs.
- License-renewal banner when their license is expired.
This eliminates relay communication ("call the plumber, ask if he can come Wednesday") — they see jobs the moment they're assigned.
Assign a job¶
From any maintenance ticket or work order → Assign:
- Pick from your artisan roster, filterable by trade + availability.
- The assign dropdown HIDES artisans who are:
- Suspended for an expired license (
ARTISAN_LICENSE_EXPIRED) - Suspended for violation-threshold breach
- Blacklisted (manual mgmt action)
- Set a target completion date.
The artisan is notified via vendor portal + email/SMS and the job appears in their queue.
Ratings (5-metric)¶
After a maintenance ticket closes, the resident is prompted to rate the work across five metrics: timeliness, quality of work, communication, professionalism, value. Each is 1-5 stars; the overall score is the average. Mgmt can also rate (separate path, recorded as a mgmt-side row).
The artisan sees their own: - Aggregate rating average + count - Free-text comments from each rating (rater identity hidden — retaliation protection) - Reputation score (derived from rating + reliability + SLA adherence)
Mgmt sees the full breakdown including rater identity (used for picking artisans for assignments + dealing with abuse cases).
License-issue-date display¶
Next to the auto-generated license number in the admin artisan form
+ artisan detail modal, the issue date is shown as
issued YYYY-MM-DD. The date is the artisan record's creation
timestamp — useful for spotting overdue-for-renewal licenses at a
glance without baking the date into the ID itself.
The format ART-DDMMYYYY-RAND8 already encodes the date inline (as
opposed to the agent format <FAC>-<RAND6>), so the separate
display is for parity with the agent UI rather than to surface
otherwise-missing info.
Payments¶
When a job is closed and approved, the artisan generates an invoice inside their portal. You see it in Artisans → Invoices:
- Approve and pay (bank transfer / mobile money / cash).
- Dispute (sends back to the artisan with notes).
- Mark paid + upload payment proof.
Reports¶
- Spend per artisan per month / year.
- Spend per trade (where the bulk of repair budget goes).
- Top performers (rating + reliability).
- At-risk artisans (declining rating, missed SLAs, recent violations).
- License renewals due — the next 30 / 60 / 90 days, so you can send chase reminders before the suspension job fires.
Multi-tenancy and the vendor portal¶
Artisans can work for multiple facilities on CautaReside. When granted portal access by a facility, they see only that facility's jobs in the queue — but their overall profile, ratings, reputation, and (importantly) suspension state roll up across every facility they serve. A successful dispute at facility A immediately clears the suspension at facility B.
Tips¶
- Register before you need them — when an emergency hits at 2 AM, you don't want to be Googling "24/7 plumber Accra". Have your roster ready.
- Set the license expiry honestly — the auto-suspension job will block assignment the moment expiry hits. Backdating to "buy time" defeats the purpose.
- Use the violation system instead of just removing artisans — the audit trail matters when an artisan disputes ("you said my work was bad but you never told me"). Filing a violation creates the record.
- Diversify — depending on a single contractor for everything is fragile. Aim for 2-3 reliable options per trade so a single suspension doesn't leave you stranded.
- Rate honestly — a 5-star review for slow / mediocre work pollutes future-assignment decisions. Better to rate accurately and keep the data trustworthy.
Process flows¶
End-to-end procedures the mgmt team runs day-to-day. Steps are anchored to the actual UI labels.
Add a new artisan to the roster¶
- Artisans → + Add Artisan.
- Capture name + phone + email + primary trade(s).
- ID type + number + Ghana-Card photo if mgmt verifies on the spot.
- License Expiry — required. Default 1 year from today.
- Save. The artisan receives an email with their vendor-portal credentials (sets their own password on first login).
- If the SOP gate is enabled, the artisan is created with
pending_first_login_sop = true— they see a blocking SOPs modal at first login.
Renew an artisan's license¶
- Artisans → open the artisan → Edit.
- Update License Expiry with the new date.
- If the renewal gate is on, the save is blocked unless the artisan has accepted the latest SOPs AND passed the training quiz since their previous expiry. The error code tells you which step is missing:
RENEWAL_REQUIRES_SOP_ACCEPTANCE— send the artisan back to the vendor portal to re-accept SOPs.RENEWAL_REQUIRES_TRAINING— same, but for the quiz.- Once the artisan completes the required step(s), retry the save. Suspension auto-clears in the same transaction.
File a violation¶
- From the artisan row → ⚠ File violation, or from the Maintenance ticket → File violation against artisan.
- Pick severity (low / medium / high / critical) — drives the auto-suspension threshold calculation.
- Capture the description + any photos (poor workmanship, missed appointment, etc.).
- Submit → artisan is notified via email + push; the violation appears on their vendor-portal profile with a Dispute affordance.
Resolve a disputed violation¶
- Artisan disputes a filed violation via their vendor portal.
- Mgmt opens the dispute thread in Artisans → row → Violations.
- Review the dispute text + any counter-evidence from the artisan.
- Action:
- Uphold — violation stands; counts toward auto-suspension thresholds.
- Dismiss — violation is voided; if the artisan was suspended by a threshold breach including this row, the suspension auto-clears on dismissal (without waiting for the nightly re-eval).
Assign an artisan to a job¶
- From a Maintenance ticket → Assign to artisan.
- The dropdown lists active, non-suspended, non-blacklisted artisans whose trade matches.
- After picking, the artisan gets a vendor-portal task with the resident contact details, location, scope, and proof-required checklist.
- The artisan replies in-portal — messages route back to the Maintenance ticket's comments thread, no email round-trip.
Rate an artisan after job completion¶
- Maintenance → ticket detail → Rate artisan (mgmt only; appears once status is Closed; resident-side prompt is separate and lower-friction).
- Rate each of the five metrics 1-5.
- Optional free-text comment.
- Submit — rating averages roll up to the artisan's profile + reputation score is recomputed and used to weight future- assignment suggestions.
Deactivate / blacklist / re-activate¶
- Deactivate — temporary, e.g. artisan is on extended leave. Hides from the assign dropdown; vendor-portal access stays. Toggle back on at any time.
- Blacklist — permanent / severe. Hides from assign dropdown AND revokes vendor portal access; flagged in audit. Restricted to the CautaReside operator.
- Re-activate from blacklist — restores assign visibility + vendor portal access. Rating history is preserved unchanged.