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.
20record types recoverable, from inventory items to payroll positions
~90named dependency checks in six kinds, each with its own message
3separate 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.
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.
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 it leans on
Recovery is one layer of a record that already knows who touched it.
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.
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.
Demo booking
Book a focused AWRA demo
Share a few details so we can send the confirmation email and route your request to the right AWRA team.
1
Request received AWRA has your demo context and contact details.
2
Check your email We send confirmation and follow-up from the AWRA team.
3
Book calendar slot Pick a time for a focused workflow walkthrough.
We use necessary cookies for secure sessions. With your permission, we also use cookies and browser storage for preferences, analytics, and demo engagement. Privacy Policy
AWRA OpsHub
Cookie settings
Cookie Consent Manager
Necessary cookies stay on for login, CSRF protection, and security. You can choose the optional categories below.
Overview
General Information
AWRA uses cookies and browser storage to keep public pages secure, remember selected preferences, measure website performance, and manage demo engagement prompts.
You can choose whether functional and marketing engagement storage apply. Analytics measurement is always active in this AWRA setup.
These settings apply to AWRA public website experiences such as the homepage, feature pages, pricing calculator, blog, help center, and request-demo page. Authenticated dashboard and vendor portal sessions still rely on required security cookies.
Required
Always active
Functional
Optional
Analytics
Always active
Marketing
Optional
For more context on privacy handling, open the Privacy Policy.
Required Cookies
Required Cookies
Always Active
Required cookies and storage support basic website delivery, secure sessions, request protection, and remembering the consent choice itself.
These cannot be switched off from this manager because disabling them would break login/session behavior, form protection, or the ability to remember the privacy choice you save.
Cookie details
Session security: keeps secure server sessions working while browsing AWRA.
CSRF protection: helps verify form submissions and protect requests.
Consent record: stores the preference decision so the banner does not keep asking after a choice is saved.
Examples: Laravel session cookies, CSRF tokens, and the AWRA consent preference record.
Duration: session security can expire with the browser/session; saved consent can last longer so the same browser remembers the choice.
Functional Cookies
Functional Cookies
Functional storage improves the public website experience by remembering interface choices, helper states, dismissed notices, and short-lived interaction preferences.
Turning this off does not stop secure required cookies or analytics. It only limits optional convenience memory.
If disabled, AWRA may show some helper prompts again or forget non-essential display choices. Core public pages, contact forms, and request-demo forms still work.
Cookie details
UI preferences: remembered display choices and helper states where available.
Dismissed notices: session-level or preference-level memory for notices the visitor has closed.
Frequency helpers: optional browser storage that prevents repeated prompts when allowed.
Examples: localStorage or sessionStorage values for dismissed banners, guide/helper states, and lightweight public-page preferences.
Effect when off: AWRA avoids optional convenience memory unless it is also allowed through marketing and engagement preferences.
Analytics Cookies
Analytics Cookies
Always Active
Analytics helps AWRA understand public page performance, traffic patterns, and content usefulness so we can improve the marketing website.
This category does not by itself enable demo popups, exit-intent prompts, or advertising pixels. Those are controlled by Marketing & engagement.
In this AWRA setup, Google Analytics and Google Tag Manager measurement are treated as mandatory website measurement and remain active.
Cookie details
Google Analytics / GTM: measures aggregate traffic and page activity.
Performance insight: helps identify which public pages, docs, and demo paths visitors use.
Operational signal: supports website quality decisions without enabling demo popups by itself.
Examples: Google measurement identifiers such as GA/GTM tags and related browser identifiers set by Google scripts.
Use: page views, source/referrer trends, public content performance, and product education page effectiveness.
Marketing & engagement
Marketing & engagement
Marketing and engagement storage supports demo prompts, exit-intent prompts, campaign attribution, and future advertising pixels.
When disabled, AWRA will not auto-open demo or exit-intent popups. CTA buttons can still open a form because that is a direct visitor action.
When enabled, AWRA can remember that a visitor already saw, dismissed, or submitted a demo prompt so the same popup is not repeated aggressively.
Cookie details
Demo prompt memory: tracks whether an auto prompt or exit prompt was recently dismissed.
Demo submission memory: avoids asking again after a visitor submits a demo request.
Campaign context: keeps source page, referrer, and UTM context available for demo requests.
Examples: popup frequency caps, demo-submitted flags, engagement source fields, and UTM/referrer context.
Effect when off: auto demo prompts and exit-intent prompts stay blocked; normal navigation and manually clicked CTA buttons still work.