Company Deletion
This guide covers how to delete companies, understanding soft delete behaviour, and the effects on users and data.
Overview
What is Company Deletion?
Shyfts uses soft deletion for companies:
| Aspect | Description |
|---|---|
| Method | Soft delete (sets is_active: false) |
| Data Retention | All data preserved |
| Reversibility | Can be reactivated |
| User Impact | All users lose access immediately |
Why Soft Delete?
| Reason | Benefit |
|---|---|
| Data Preservation | Historical records maintained |
| Compliance | Meets data retention requirements |
| Recovery | Accidental deletions recoverable |
| Audit Trail | Complete history preserved |
Delete Company API
Endpoint
Endpoint: DELETE /api/companies/[companyId]
// Source: src/app/api/companies/[companyId]/route.ts:166-192
export async function DELETE(_request: NextRequest, { params }: RouteParams) {
try {
const resolvedParams = await params
const companyId = resolvedParams.companyId
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return ApiResponses.unauthorized()
}
// Check if user is system admin
if (getAuthRole(user) !== 'SYSTEM_ADMIN') {
return ApiResponses.forbidden('Forbidden - System Admin access required')
}
// Soft delete the company
const result = await companyDB.deleteCompany(companyId)
return ApiResponses.success({ message: 'Company deleted successfully', ...result.data })
} catch (error) {
return handleApiError(error)
}
}
Soft Delete Implementation
// Source: src/lib/db/company.db.ts:212-223
export async function deleteCompany(id: string): Promise<{ data: CompanyComponentType, timing: { duration: number } }> {
const { result, timing } = await trackPerformance('deleteCompany', async () => {
const supabase = await createClient();
const { data: company, error } = await supabase
.from('companies')
.update({ is_active: false, updated_at: new Date().toISOString() })
.eq('id', id)
.select(`*,
industry_templates (*)`)
.single();
if (error) handleDBError(error);
// ... adapter conversion
});
}
Request Format
// Delete company request
const deleteCompany = async (companyId: string) => {
const response = await fetch(`/api/companies/${companyId}`, {
method: 'DELETE'
})
if (!response.ok) {
throw new Error('Failed to delete company')
}
return response.json()
}
Response Format
// Successful deletion response
{
success: true,
data: {
message: "Company deleted successfully",
id: "company-uuid",
name: "Example Practice",
is_active: false,
updated_at: "2025-01-14T10:30:00Z"
}
}
Deletion Effects
Immediate Effects
When a company is deleted (soft delete):
| Component | Effect |
|---|---|
| Company Status | Set to inactive |
| Company Managers | Cannot login |
| Staff Members | Cannot login |
| Schedules | Frozen, inaccessible |
| Data | Preserved but inaccessible |
| API Access | Blocked |
Data Preservation
| Data Type | Status | Accessibility |
|---|---|---|
| Company Record | Preserved | System Admin only |
| Staff Records | Preserved | System Admin only |
| Shift History | Preserved | Not accessible |
| Leave Records | Preserved | Not accessible |
| Time Entries | Preserved | Not accessible |
| Documents | Preserved | Not accessible |
Pre-Deletion Checklist
Before Deleting
Review these items before deletion:
| Item | Action | Priority |
|---|---|---|
| Active Users | Count and notify | High |
| Pending Shifts | Review future schedules | High |
| Leave Requests | Process pending requests | Medium |
| Outstanding Time | Approve/reject entries | Medium |
| Documents | Export if needed | Low |
| Reports | Generate final reports | Low |
Pre-Deletion Report
// Get pre-deletion summary
const getPreDeletionReport = async (companyId: string) => {
const [company, staffCount, pendingShifts, pendingLeave] = await Promise.all([
fetch(`/api/companies/${companyId}`).then(r => r.json()),
fetch(`/api/companies/${companyId}/staff/count`).then(r => r.json()),
fetch(`/api/companies/${companyId}/shifts?status=pending`).then(r => r.json()),
fetch(`/api/companies/${companyId}/leave?status=pending`).then(r => r.json())
])
return {
company: company.data,
totalStaff: staffCount.data.count,
pendingShifts: pendingShifts.data.length,
pendingLeave: pendingLeave.data.length
}
}
Deletion Process
Step-by-Step
1. Navigate to company details
2. Review pre-deletion checklist
3. Click "Delete Company" button
4. Review impact summary
5. Enter confirmation (company name)
6. Click "Confirm Delete"
7. Company soft deleted
8. Users notified
Confirmation Requirements
| Requirement | Purpose |
|---|---|
| Enter Company Name | Prevents accidental deletion |
| Review Impact | Understand consequences |
| Double Confirmation | Extra safety measure |
Deletion Confirmation Modal
Component Example
// Delete confirmation modal
const DeleteConfirmModal = ({ company, onConfirm, onCancel }) => {
const [confirmName, setConfirmName] = useState('')
const [loading, setLoading] = useState(false)
const isConfirmed = confirmName.toLowerCase() === company.name.toLowerCase()
const handleDelete = async () => {
if (!isConfirmed) return
setLoading(true)
try {
await onConfirm()
} finally {
setLoading(false)
}
}
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="card-glass p-6 max-w-md w-full mx-4">
<h2 className="text-xl font-semibold text-red-400 mb-4">
Delete Company
</h2>
<div className="space-y-4">
<p className="text-secondary-text">
This will immediately block access for all {company.staffCount} users.
</p>
<div className="bg-red-500/10 border border-red-500/20 rounded p-3">
<p className="text-sm text-red-300">
<strong>Warning:</strong> All company managers and staff will be
unable to login. This action can be reversed by reactivating the company.
</p>
</div>
<div>
<label className="block text-sm font-medium mb-2">
Type <strong>{company.name}</strong> to confirm
</label>
<input
type="text"
value={confirmName}
onChange={(e) => setConfirmName(e.target.value)}
className="form-input w-full"
placeholder="Enter company name"
/>
</div>
<div className="flex gap-3 pt-2">
<button
onClick={onCancel}
className="form-button secondary flex-1"
disabled={loading}
>
Cancel
</button>
<button
onClick={handleDelete}
disabled={!isConfirmed || loading}
className="form-button danger flex-1"
>
{loading ? 'Deleting...' : 'Delete Company'}
</button>
</div>
</div>
</div>
</div>
)
}
Recovery Options
Reactivating Deleted Companies
Since deletion is soft, companies can be reactivated:
// Reactivate a deleted company
const reactivateCompany = async (companyId: string) => {
const response = await fetch(`/api/companies/${companyId}/toggle-status`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ active: true })
})
return response.json()
}
Recovery Process
1. Navigate to Admin Portal
2. View inactive companies filter
3. Select the deleted company
4. Click "Reactivate Company"
5. Confirm reactivation
6. Company and users regain access
Recovery Limitations
| Limitation | Description |
|---|---|
| Time Limit | No hard limit (data preserved indefinitely) |
| Data Integrity | All original data restored |
| User Accounts | All accounts restored |
| Permissions | Original permissions restored |
Permanent Deletion
Hard Delete (Future Feature)
Not Currently Available
Permanent deletion is not currently implemented. All deletions are soft deletes that can be reversed.
When Permanent Delete May Be Needed
| Scenario | Reason |
|---|---|
| GDPR Request | Data subject right to erasure |
| Compliance | Regulatory requirement |
| Storage | Long-term inactive data cleanup |
Data Subject Requests
For GDPR erasure requests:
- Document Request - Record the formal request
- Verify Identity - Confirm requester identity
- Assess Scope - Determine what data to erase
- Contact Support - Manual database intervention required
- Confirm Deletion - Provide confirmation to requester
Audit Trail
Deletion Logging
All deletions are logged:
// Deletion activity log entry
{
action: 'company_deleted',
company_id: 'company-uuid',
created_by: 'system-admin-uuid',
details: {
company_name: 'Example Practice',
staff_count: 15,
manager_count: 2,
reason: 'Contract terminated'
},
created_at: '2025-01-14T10:30:00Z',
ip_address: '192.168.1.1'
}
Viewing Deletion History
// Get deletion history
const getDeletionHistory = async () => {
const { data } = await supabase
.from('system_activity_logs')
.select('*')
.eq('action', 'company_deleted')
.order('created_at', { ascending: false })
return data
}
User Notifications
Notification Flow
When a company is deleted:
| Recipient | Notification | Method |
|---|---|---|
| Company Managers | Account suspended | Email (if configured) |
| Staff Members | Access revoked | Email (if configured) |
| System Admins | Deletion confirmed | Dashboard notification |
Notification Template
// Deletion notification content
{
subject: "Your Shyfts Account Has Been Suspended",
body: `
Dear {firstName},
Your company ({companyName}) account on Shyfts has been suspended.
You will no longer be able to access the platform.
If you believe this is an error, please contact your administrator.
Regards,
Shyfts Team
`
}
Best Practices
Before Deletion
| Practice | Description |
|---|---|
| Export Data | Generate reports for records |
| Notify Users | Give advance warning when possible |
| Process Pending | Handle outstanding requests |
| Document Reason | Record why company was deleted |
After Deletion
| Practice | Description |
|---|---|
| Verify Access Blocked | Confirm users cannot login |
| Update Records | Note deletion in external systems |
| Retain Documentation | Keep deletion records |
| Monitor Recovery Requests | Watch for reactivation needs |
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| Deletion failed | Network error | Retry operation |
| Users still have access | Cache delay | Wait 5 minutes |
| Company not found | Invalid ID | Verify company exists |
| Forbidden error | Not System Admin | Check permissions |
Error Messages
| Error | Meaning | Action |
|---|---|---|
| "Company not found" | Invalid company ID | Verify company exists |
| "Forbidden" | Not authorised | Login as System Admin |
| "Database error" | Server issue | Contact support |
| "Deletion in progress" | Already processing | Wait and retry |
API Reference Summary
Delete Endpoint
| Property | Value |
|---|---|
| Method | DELETE |
| Endpoint | /api/companies/[companyId] |
| Authentication | Required |
| Role Required | SYSTEM_ADMIN |
| Soft Delete | Yes (sets is_active = false) |
Response Codes
| Code | Meaning |
|---|---|
| 200 | Successfully deleted |
| 401 | Not authenticated |
| 403 | Not authorised (not System Admin) |
| 404 | Company not found |
| 500 | Server error |
Related Documentation
- Creating Companies - Create new companies
- Editing Companies - Update details
- Company Status - Activate/deactivate
- Viewing Users - User management
Source Files:
src/app/api/companies/[companyId]/route.ts- Company delete APIsrc/lib/db/company.db.ts- Soft delete implementationsrc/types/database.types.ts- Database type definitions