AWRA OpsHub Search
Trash & record recovery

Deleting is easy. Knowing what the delete would break is the product.

Every serious system has an undo. What almost none of them have is a permanent delete that reads the record's own history first — the check-ins, the journal entries, the payroll runs, the signed tickets — and refuses when removing the row would leave a document pointing at nothing. AWRA has roughly ninety of those checks, written per record type, and they are the reason the recovery centre is safe to give to an operations manager.

20 record types recoverable, from inventory items to payroll positions
~90 named dependency checks in six kinds, each with its own message
3 separate permissions: see it, restore it, erase it

Two words that get used as one

“Delete” means two entirely different things, and conflating them is how data disappears.

A user pressing delete almost always means take this off my screen. Occasionally somebody means remove this from the database forever. Systems that offer one button for both eventually grant the second intention to somebody who had the first. AWRA separates them into two actions, two permissions and two completely different sets of rules.

Reversible

Move to Trash

The row stays in the database with a deletion timestamp on it. It vanishes from lists, reports and searches, and appears in the recovery centre. Nothing that referenced it breaks, because nothing has gone.

  • Gated on manage_trash to view, and the module's own delete permission to put something there.
  • Restore puts it back where it was, with its identifiers intact — not as a copy with a new number.
  • A parent brings its children back with it. Restoring a project returns the milestones and sprints deleted alongside it; restoring a customer returns its contacts and addresses.
  • Two restores are refused on purpose, and only two: a user whose data was erased under a privacy request, and a child record whose parent is still in the bin.
Irreversible

Permanent delete

The row is removed. There is no second bin behind this one, which is exactly why the action has to argue its case before it runs — against the record's dependencies, against any hold, and in the riskiest cases against a second person.

  • A separate permission, delete_trash, that you can grant to nobody and still have a fully working recovery centre.
  • Runs the record's full dependency set first and reports every failure by name, not as one generic error.
  • Blocked outright while a legal hold covers the record or its whole type.
  • On the high and critical record types, needs an approval from somebody holding approve_trash_deletes — and the requester cannot be that somebody.

Why the refusal is the feature

A record is never alone. It is the middle of a fan of documents that quietly assume it exists.

Delete an inventory item and you have not deleted an item — you have orphaned every goods receipt, stock lot, invoice line and journal entry that named it. The receipt still exists. It just now describes something with no name. Six months later somebody opens that receipt during an audit and finds a blank where the product was, and nobody can tell them whether the row was removed on purpose or lost.

Goods receipt (check-in) Stock lot / trace record Customer invoice line Purchase order line Stock transfer line Asset conversion POS sale line Count session line Inventory item

The check runs before the delete, not after it.

Every record type in the recovery centre carries its own list of things to look for, written as data rather than buried in a controller. Each entry has a code, a severity and a sentence a human can read. When a permanent delete is attempted, the list is walked and the person is shown exactly which references stopped it — three named documents, not “cannot delete: constraint violation”.

The same list powers a preview, so nobody has to attempt a destructive action to learn whether it would work. You open the impact preview, read what would be affected, and decide with the answer in front of you.

The six kinds of check

Every check is one of six shapes, which is what makes ninety of them reviewable.

A dependency rule written as bespoke code is a rule nobody can audit. Written as one of six declared shapes, the whole set can be read in an afternoon, argued about, and extended for your own tables without touching a controller.

Check
Count
What it asks
table_reference_exists
67

Does any row in a named table still carry this record's id in a named column — within this workspace? This is the workhorse: check-ins, check-outs, invoices, payments, journal entries, payslips, attendance, conversation threads.

relationship_exists
12

Does a declared Eloquent relationship still return anything? Used where the join is more complicated than one column, so the model already knows how to answer it.

field_not_null
6

Does the record itself still point outward — a procurement request id, a quotation id, a customer invoice id sitting on the row? A link that starts at the record counts as much as one that ends there.

child_records
5

Which rows were deleted alongside this one and should come back with it? This one is not a blocker: it is how restoring a project returns its milestones and sprints.

belongs_to_trashed
4

Is this record's parent still in the bin? Restoring a child into a deleted parent produces a row nobody can navigate to, so the restore waits for the parent.

json_reference_exists
1

Is the record named inside a JSON column rather than a foreign key? Rare, and exactly the kind of link a database constraint cannot see.

Coverage

Twelve modules, twenty record types, and a stated policy for each.

Recovery is not a single behaviour bolted onto a table. Each module gets a policy that says what restore means there, what blocks erasure, and why — because “delete a leave type” and “delete a user” are not the same risk and should not be governed by the same sentence.

Inventory

Restorable. Permanent delete waits until stock, trace, media and accounting impacts are clear.

  • Adjustments
  • Items

Asset tracking

Restorable while custody links are valid. Erasure blocked while custody, movement or pool history exists.

  • Assets
  • Custodians

Procurement

Master data recovers. Anything tied to an order, a receipt or a vendor file moves toward retain-only.

  • Vendors

Sales & CRM

Master data recovers and child contact and address rows follow the customer back.

  • Customers

HR & people

Employment history is evidence a labour dispute can demand years later, so pay, attendance and custody history all block erasure.

  • Employees
  • Positions
  • Leave types
  • Shifts
  • Holidays

Projects & delivery

A project restores with its milestones and sprints. Once a job carries cost or billing history, erasure is blocked.

  • Projects
  • Tasks

Helpdesk

Tickets and categories recover. A surviving conversation blocks erasure, and a signed ticket is retain-only.

  • Tickets
  • Categories

Expenses

An unapproved claim is ordinary data. Once approved it is an authorised spend decision and becomes retain-only.

  • Expenses

Settings & custom fields

Tenant schema recovers. A definition cannot be erased while it still gives meaning to stored values.

  • Definitions
  • Options
  • Values

Admin & security

Highest risk. Restore refused for a privacy-erased user; erasure refused for the person attempting it.

  • Users

Finance & accounting

Deliberately outside general force-delete until reversal, period-close, tax and retention rules are explicit.

  • Policy stated

Communications

Owner-scoped recovery, with erasure held until attachment cleanup and ownership are unambiguous.

  • Policy stated

The last two are worth reading twice, because they are the honest ones. Finance and communications each have a written policy and no general permanent-delete path, and that is deliberate rather than unfinished: a ledger row and an attachment with unclear ownership are exactly the records where a vendor's confidence should make you nervous.

What actually happens

From “somebody deleted the wrong vendor” to a decision nobody has to defend.

Six steps, of which four are optional depending on how much governance your organisation actually wants. A two-person warehouse can use step one and step two and ignore the rest; a donor-funded programme with an annual audit will want all six.

It lands in the recovery centre

One screen, filterable by record type and searchable by name or id, showing everything soft-deleted in the workspace with who deleted it and when. Records from another workspace are not hidden behind a permission check on the web — they return unauthorised; on the API they are simply not there, because a 403 on a specific id is itself a disclosure.

Someone restores it, and its children come back too

Restore returns the record with its original identifiers, so every document that referenced it is correct again rather than pointing at a replacement. Where a parent was deleted with its children, restoring the parent returns the set — the three declared groups are project milestones and sprints, customer contacts and addresses, and ticket thread rows.

Or someone asks to erase it, and sees the impact first

The impact preview runs the record's whole dependency list without deleting anything and returns the result as a list of named findings. This is the step that changes the conversation from "can I delete this" to "these four documents would be affected, and here they are".

The preview is read-only and needs no destructive permission. Anyone who can see the record can see what erasing it would touch.

A hold can freeze it, individually or by type

A legal hold stops erasure. It can cover one record or an entire record type, carries the reason it was placed and who placed it, and can be given an expiry date so a litigation hold does not quietly become permanent. Releasing it records who released it and why.

A type-scope hold is the one to reach for when counsel says "nothing in expenses gets deleted until this is settled" — you do not have to find and pin every row.

On the risky types, a second person decides

The five critical and six high-risk record types route a permanent delete through an approval run before anything is removed. The separation is enforced rather than advisory: the person who requested the erasure cannot approve it and cannot be the one who executes it.

Segregation of duties is configured, not assumed — requester-cannot-approve and requester-cannot-execute are both on by default.

Everything above is written to a lifecycle ledger

Each event — a restore, a refusal, a hold placed, an approval granted, a purge — is recorded with the actor, the surface it came from, the route, the IP and the user agent, and cross-referenced to the audit log entry, the approval run and the hold it belongs to. The question "who deleted the March vendor file" has an answer with a name in it.

Legal holds

The one control that has to beat everybody, including the person who owns the workspace.

A retention timer decides when something goes. A hold decides that nothing goes, regardless of the timer, regardless of the permission, until the hold is lifted by somebody who has to say why. These are opposite jobs and they are two separate mechanisms on purpose.

Record scope

Pin one row

A specific expense claim under query, one vendor whose file is part of an investigation, a single employee record named in a dispute. The hold carries a reason, a start, an optional expiry, and the person who placed it. Erasure of that record is refused while it stands — before the dependency checks even run, because a hold is not a matter of what the record is wired to.

Type scope

Freeze a whole category

“Nothing in expenses is erased until the audit closes.” One hold covers the type, applies to rows that arrive after it was placed, and does not need anybody to keep a list. This is the difference between a hold you can actually rely on and a policy you have to police.

Event
Actor
Recorded with it
Cross-linked
restore
A. Kimani
Record type, id, title at time of restore, surface (web / API / mobile), route, method, IP, user agent
Audit log
force_delete_blocked
J. Mwangi
Every failing check by code and message, and the risk level of the record type
Audit log
legal_hold_placed
Counsel (M. Ouma)
Scope, reason, start date, expiry if set
Hold record
approval_requested
J. Mwangi
Record, risk level, the permission required to decide it
Approval run
approval_granted
F. Njeri
Comment, decision time, and the fact that the requester was a different person
Approval run
purged
System (scheduled)
Retention policy that matched, run id, dry-run flag
Purge report

Retention & scheduled purge

A bin that never empties is a second database nobody is looking after.

Soft-deleted rows are still your data: still in backups, still subject to whatever your privacy commitments say, still counted in your storage. So retention is real rather than decorative — a policy per record type, a scheduled job that applies it, a dry run that shows what a real run would take, and a report of every run kept afterwards.

It is off until you turn it on. That default is the honest one: a system that starts permanently deleting your records on day 31 because nobody read the settings screen has made a decision that was not its to make.

Shipped defaults, once retention is switched on

Configurable per record type between 1 day and 3,650 days. The split below is not arbitrary — the 90-day set is the identity, people and asset data where a mistake takes longer to notice.

Users
90 days
Customers
90 days
Assets & custodians
90 days
Employees & positions
90 days
Leave types & shifts
90 days
Projects
90 days
Expenses
90 days
Custom field definitions & options
90 days
Items
30 days
Inventory adjustments
30 days
Vendors
30 days
Tasks & tickets
30 days
Public holidays
30 days

Dry run first

A purge can be run in dry mode: it evaluates every policy, decides exactly what a real run would remove, and writes the report — without touching a row. Read it, then decide whether to let the real one go.

A report per run

Each run gets an id, and each report is kept and readable afterwards, per record type. “What did the purge take last month” is a page you open rather than a log you grep.

Alerts on both failure modes

A single failed purge raises an alert and three make it critical. Separately, unusual force-delete volume is watched — ten in a window, or three times the normal rate, and somebody hears about it before twenty-five.

Where you use it

One set of rules, three surfaces, no second implementation to drift.

The registry that declares record types, permissions, blockers and routes is read by the web controller, the API controller and the mobile admin contract. That is the point of writing it as data: a check added once applies everywhere, and no surface can quietly be more permissive than another.

The recovery centre in the browser

Filter by record type, search, open a record's detail, run an impact preview, restore, request erasure, place or release a hold, and approve or reject somebody else's request — all on one screen, each action gated on its own permission.

The API, including bulk

List, restore, erase and preview over the API, plus three operations that only make sense in volume: bulk restore, bulk permanent delete and bulk export of what is in the bin. A migration that went wrong at 3am is not a job for a mouse.

Mobile admin

The app is served a contract describing what it may show and do, generated from the same registry, so the phone cannot offer an action the server would refuse. Approving somebody's erasure request is something you can do from a car park.

The straight answer

Read this before you write “full data recovery” into a policy document.

Recovery is a strong operational control and it is not a backup. Those are different things and a vendor who lets you conflate them is selling you a false sense of safety. Here is the line, drawn where we would want it drawn if we were the ones buying.

Trash & record recovery — what is real

What AWRA OpsHub does today

  • Twenty record types recoverable across inventory, procurement, sales, assets, HR, projects, helpdesk, expenses and tenant schema — restore returns the original identifiers, not a copy.
  • Around ninety named dependency checks in six declared shapes, each with a code, a severity and a sentence a person can read, so a refused erasure says which documents refused it.
  • An impact preview that needs no destructive permission — you can see what erasing something would touch without being able to erase it.
  • Legal holds at record and type scope, with a reason, an optional expiry, and a named person on both the placing and the releasing.
  • A second approver on the eleven high and critical record types, with requester-cannot-approve and requester-cannot-execute enforced rather than recommended.
  • Retention policies per record type, from 1 to 3,650 days, applied by a scheduled purge that can be dry-run and that writes a keepable report of every run.
  • A lifecycle ledger recording every restore, refusal, hold, approval and purge with actor, surface, route, IP and user agent, cross-linked to the audit log entry.
  • Three separate permissions — see, restore, erase — plus a fourth to approve an erasure, so you can run a recovery centre where nobody can permanently delete anything.
  • Bulk restore, bulk erase and bulk export over the API, for the cases where a mouse is the wrong tool.
  • Alerting on purge failure and on unusual erasure volume, with a critical threshold above the warning one.

More we can add to your workspace

  • Ledger and invoice records inside the general recovery centre. Finance has a written retain-only policy today rather than a force-delete path, because reversal, period-close, tax and legal retention rules have to be explicit before erasure is offered. Commissioning that is a scoping conversation about your jurisdiction, not a switch.
  • Purchase orders, quotations and customer invoices as recoverable record types, with the document-lifecycle rules that would have to govern them.
  • Draft email and attachment recovery scoped to the owner, with the attachment-cleanup rules that make erasure unambiguous.
  • A self-service retention policy editor with per-record overrides in the browser, on top of the policy engine and defaults already shipped.
  • Point-in-time recovery of a whole workspace to a chosen timestamp, which is a backup and restore product rather than a record-level one.
  • Cascading restore across module boundaries — bringing back a vendor and, with it, every purchase order deleted in the same afternoon.

Where we point you to a specialist

  • A soft-deleted row is not erased data, and we will not describe it as such. It is still in the database and in backups. If a privacy request requires actual erasure, that is the privacy erasure path, and it is a different mechanism with different consequences — including that the user it applies to can no longer be restored here.
  • We will not offer permanent delete of a record whose history still needs it. You can widen the retention window, place a hold, or ask us to build a lifecycle rule for the document type. What we will not do is add a switch that skips the checks, because the value of the checks is that they cannot be skipped.
  • We will not advise you on your statutory retention period. Seven years, ten years, indefinitely for employment records in your jurisdiction — that is your auditor's and your counsel's call. We give you a policy engine that can hold any figure they give you, and a hold that beats it.
  • Trash is not a backup and is not a substitute for one. It recovers a record somebody deleted. It does not recover from a failed migration, a corrupted table or a lost disk, and treating it as though it does is the mistake this section exists to prevent.

Everything in the middle column is scope rather than a ceiling — the finance and document-lifecycle rules in particular are a design conversation before they are a build. Tell us which of it your audit actually needs and we will come back with a written spec, a timeline and a price to add it to your workspace.

What changes

The deletion conversation stops being a crisis and starts being a screen.

Nothing here stops people deleting the wrong thing — they will, on a Friday, at speed. What changes is how long it takes to put right, and whether the fix leaves a hole somewhere else.

A wrong delete is a two-minute fix

Not a support ticket, not a database restore, not an evening. Find it, read what it was, put it back with its own id.

Orphaned documents stop happening

The receipt that names a product still names it, because the row it named could not be removed while it was needed.

Erasure has a paper trail with a name on it

Who asked, who approved, what the checks said, when it ran — one ledger entry, cross-linked to the audit log.

A hold you can actually rely on

Counsel says freeze expenses; you place one hold on the type and stop maintaining a list.

The bin stops growing forever

A retention policy you set, applied by a job you can dry-run, reported in a file you can keep.

Nobody needs the erase permission

Grant see-and-restore widely and erase to nobody, and the recovery centre still does its whole job.

Frequently asked questions

What can actually be restored?
Twenty record types across twelve modules: inventory items and adjustments, vendors, customers, assets and asset custodians, users, employees, positions, leave types, shifts, public holidays, projects, tasks, helpdesk tickets and ticket categories, expenses, and the three custom-field record types (definitions, options and values). A restore returns the record with its original identifiers rather than as a new copy, so documents that referenced it are correct again. Where a parent was deleted with its children, restoring the parent brings the set back — project milestones and sprints, customer contacts and addresses, and ticket thread rows are the three declared groups.
Can someone permanently delete a record that other documents still reference?
No, and this is the part of the feature worth pressing us on. Each record type carries its own declared list of dependency checks — roughly ninety across the registry, in six shapes: a reference from a named table and column, a declared model relationship, an outward link held on the record itself, a JSON reference, a parent still in the bin, and a child-record group. A permanent delete runs the whole list first and reports every failure by name and message. Restore stays available; erasure does not. There is no override switch, because the value of the checks is precisely that they cannot be skipped.
Is Trash a backup?
No. It recovers a record a person deleted, and it does that well. It does not recover from a failed migration, a corrupted table or lost storage — that is backup and restore, a different mechanism with different guarantees. It is also worth being clear that a soft-deleted row is not erased data: it is still in the database and in backups, so if a privacy request requires actual erasure, that runs through the privacy erasure path instead, and a user erased that way can no longer be restored here.
Who can permanently delete something?
Only somebody holding a permission separate from the one that lets them see and restore. Three permissions are involved: one to view and restore, one to permanently delete, and one to approve somebody else's permanent delete. You can run a complete recovery centre with the erase permission granted to nobody. On the eleven record types classed high or critical — identity, people, assets and financial-adjacent data — an erasure additionally routes through an approval run, and the person who requested it cannot approve it or execute it.
What is a legal hold, and how is it different from retention?
They do opposite jobs. Retention decides when something is eventually removed; a hold decides that nothing is removed regardless of the timer or the permission. A hold can cover one record or an entire record type, carries the reason it was placed and who placed it, can be given an expiry so a litigation hold does not silently become permanent, and records who released it and why. A type-scope hold is the one to use when counsel says nothing in a category gets deleted until a matter is settled — you do not have to find and pin every row, including the ones created after the hold.
Does the bin empty itself?
Only if you tell it to. Retention is off by default — a system that starts permanently deleting records on day 31 because nobody read the settings screen has made a decision that was not its to make. When you switch it on, each record type has a policy configurable between 1 and 3,650 days, with shipped defaults of 90 days for identity, people and asset data and 30 days for operational records. A scheduled purge applies the policies, can be run in dry mode that reports exactly what a real run would remove without touching anything, and writes a keepable report for every run. Purge failures alert at one and go critical at three.
Can we do this from the API or the mobile app?
Yes, and it is the same rules rather than a second implementation. The registry that declares record types, permissions, blockers and routes is read by the web controller, the API controller and the contract served to the mobile admin screens, so a check added once applies on every surface and no surface can be more permissive than another. The API additionally exposes three operations that only make sense in volume: bulk restore, bulk permanent delete and bulk export of what is currently in the bin.
What is written down when something is restored or erased?
A lifecycle ledger entry, separate from and cross-referenced to the audit log. It records the event type, the record type and id, the title the record had at the time, the risk level, the actor with their name and email, the surface the action came from, the route and HTTP method, the IP address and the user agent, and the identifiers of the related audit-log entry, approval run, hold and retention policy where those apply. Refusals are recorded too — a blocked erasure is an event, not a silence.

Help Center

Need a quick answer while you read?

Run inventory, procurement, assets, sales, and field work with approved AWRA guidance for setup, migration, integrations, security, pricing, and support.

Search all approved AWRA public help articles.

Open Help Center