Document tracking — every expiry, surfaced
15 Oman-specific document types. Each upload tracks issue date, expiry date, issuing authority, doc number, and storage. Colour-coded expiry badges (red expired, orange ≤30d, info ≤90d, green safe). M17 reminder cron fires at T-30, T-7, T-1 days before expiry.
The 15 doc types
| Type | Typical issuer | Renewal urgency |
|---|---|---|
| Civil ID | ROP Civil Status | Critical — required for everything |
| Passport | Home country | Visa renewals require > 6 months remaining |
| Visa / residence card | ROP Foreign Affairs | Critical — overstay = OMR 10/day fine |
| Driving licence | ROP Traffic | Driving without is a fine + visa risk |
| Medical card | MOH | Required for visa renewal |
| Offer letter | Firm | One-time |
| Employment contract | Firm + MOLP | Renewal annual or 2-year |
| NDA | Firm | One-time |
| Bank letter | Bank | For salary deposit account |
| PASI card | PASI | Omani only |
| Education certificate | University / school | One-time, MOCIIP-attested for senior roles |
| Experience letter | Prior employer | One-time per prior role |
| Resume | Self | Updated periodically |
| Photo | Self | Used on payslips, ID badges |
| Other | — | Catch-all |
Per-document fields
Drives icon, renewal logic, default expiry alert lead-time.
The actual reference number on the document. Searchable.
When issued. Drives some compliance calcs (e.g. visa duration).
Drives the colour-coded badge. Null = never expires (e.g. educational certs).
e.g. "ROP Civil Status", "Embassy of India · Muscat".
Renewal instructions, fee, where to go.
PDF / JPG / PNG / WebP / DOC / DOCX. Stored at storage/employees/{id}/docs/{uuid}.{ext}.
SQL-derived: DATEDIFF(expiry_date, CURDATE()). Negative = expired.
M17 cron checks this to avoid re-sending in 24h.
The expiry-status badge
| Days to expiry | Badge | Action |
|---|---|---|
| < 0 | EXPIRED N days ago | Renew immediately; document is non-compliant |
| 0-30 | Expires in N days | Renewal in motion; cron has already pinged |
| 31-90 | Info — Expires in N days | On the radar; cron will ping at T-30 |
| > 90 | Valid | No action |
| NULL | Permanent | e.g. degree certificate, NDA |
The reminder cron
bin/cron-m17-expiry-reminders.php runs nightly at 06:00 Asia/Muscat. For each employee_documents row where expiry_date - CURDATE() <= reminder_lead_days (default 30):
- Check
last_reminder_sent_at> 24h ago (idempotent) - Resolve recipient — employee.work_email (primary) + manager (cc) + HR partner (cc if < 7 days)
- Queue email via M17 with template
compliance.expiry_reminder, merge employee + doc tokens - Stamp
last_reminder_sent_at = NOW() - Audit-log entry
Step-by-step — uploading a document
Open employee → Documents tab
26-tab strip on the employee detail page. Click Documents. Existing docs render as cards with file icon + expiry badge.
Click "+ Upload"
Inline form: pick doc_type from dropdown (auto-fills suggested expiry interval), enter doc_number, dates, authority. Drag-drop file or click to choose.
Save
System validates (extension whitelist, MIME sniff, size ≤ 15 MB). UUID rename. SHA-256 stored. Audit-logged.
Document appears with badge
Card renders with colour-coded expiry. Click the file-name to download (auth-gated, integrity-checked).
Edit / replace
Each card has edit + delete. Edit opens the form pre-filled. Replacing the file uploads a new version (old retained as audit history).
Open any employee → Documents → upload a fake "Visa" PDF with expiry_date = today + 25 days. Save. Watch the orange "Expires in 25 days" badge appear immediately. Check tomorrow's M17 email queue (or run the cron manually) — a reminder email is queued with the doc number + employee + days-to-expiry merged in.
Visa expiry < 90 days has visa-renewal lead-time implications. Some authorities won't process renewals if < 30 days. Set the alert threshold to 60 days for visas + civil ID via m17.expiry_reminder_days. The default 30 is fine for less critical docs.
Use Reports → Expiring Documents for a firm-wide list of all docs expiring in next 60 days, grouped by employee. Export to CSV, send to HR coordinator weekly. Catches the doc that fell through the M17 reminder pipeline (e.g. employee on leave, email bounced).