AAuditPro Suite· HR manual
HR manual Wave 2a · Documents

The 15 doc types

TypeTypical issuerRenewal urgency
Civil IDROP Civil StatusCritical — required for everything
PassportHome countryVisa renewals require > 6 months remaining
Visa / residence cardROP Foreign AffairsCritical — overstay = OMR 10/day fine
Driving licenceROP TrafficDriving without is a fine + visa risk
Medical cardMOHRequired for visa renewal
Offer letterFirmOne-time
Employment contractFirm + MOLPRenewal annual or 2-year
NDAFirmOne-time
Bank letterBankFor salary deposit account
PASI cardPASIOmani only
Education certificateUniversity / schoolOne-time, MOCIIP-attested for senior roles
Experience letterPrior employerOne-time per prior role
ResumeSelfUpdated periodically
PhotoSelfUsed on payslips, ID badges
OtherCatch-all

Per-document fields

1document_type
Lookup · 15 types

Drives icon, renewal logic, default expiry alert lead-time.

2doc_number
Free text

The actual reference number on the document. Searchable.

3issue_date
Date

When issued. Drives some compliance calcs (e.g. visa duration).

4expiry_date
Date · nullable

Drives the colour-coded badge. Null = never expires (e.g. educational certs).

5issuing_authority
Free text

e.g. "ROP Civil Status", "Embassy of India · Muscat".

6notes
Free text

Renewal instructions, fee, where to go.

7file
Upload · 15 MB

PDF / JPG / PNG / WebP / DOC / DOCX. Stored at storage/employees/{id}/docs/{uuid}.{ext}.

8days_to_expiry
Computed

SQL-derived: DATEDIFF(expiry_date, CURDATE()). Negative = expired.

9last_reminder_sent_at
Timestamp · idempotent guard

M17 cron checks this to avoid re-sending in 24h.

The expiry-status badge

Days to expiryBadgeAction
< 0EXPIRED N days agoRenew immediately; document is non-compliant
0-30Expires in N daysRenewal in motion; cron has already pinged
31-90Info — Expires in N daysOn the radar; cron will ping at T-30
> 90ValidNo action
NULLPermanente.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):

  1. Check last_reminder_sent_at > 24h ago (idempotent)
  2. Resolve recipient — employee.work_email (primary) + manager (cc) + HR partner (cc if < 7 days)
  3. Queue email via M17 with template compliance.expiry_reminder, merge employee + doc tokens
  4. Stamp last_reminder_sent_at = NOW()
  5. Audit-log entry

Step-by-step — uploading a document

  1. Open employee → Documents tab

    26-tab strip on the employee detail page. Click Documents. Existing docs render as cards with file icon + expiry badge.

  2. 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.

  3. Save

    System validates (extension whitelist, MIME sniff, size ≤ 15 MB). UUID rename. SHA-256 stored. Audit-logged.

  4. Document appears with badge

    Card renders with colour-coded expiry. Click the file-name to download (auth-gated, integrity-checked).

  5. 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).

Try this

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.

Watch out

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.

Tip — bulk audit

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).