296 lines
10 KiB
Markdown
296 lines
10 KiB
Markdown
# Job Postings Feature — Implementation Plan
|
|
|
|
## 1. Overview
|
|
|
|
This feature has two sides:
|
|
|
|
- **Admin side** — create/manage job position templates, publish open positions, review applicant submissions.
|
|
- **Client side** — browse open positions, view details, and submit an application (with resume upload) that triggers a confirmation email.
|
|
|
|
---
|
|
|
|
## 2. Data Model
|
|
|
|
### JobTemplate
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `template_id` | UUID | Primary key |
|
|
| `title` | string | |
|
|
| `description` | text | |
|
|
| `department` | string | |
|
|
| `location` | string | |
|
|
| `employment_type` | string | e.g. full-time, part-time, contract |
|
|
| `requirements` | text | |
|
|
| `salary_range` | string | optional |
|
|
| `version` | int | increments on each edit |
|
|
| `is_active` | bool | soft-archive instead of delete |
|
|
| `created_by` | user ref | |
|
|
| `created_at` / `updated_at` | timestamp | |
|
|
|
|
### JobPosition
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `position_id` | UUID | **Unique ID per job, required** |
|
|
| `template_id` | UUID (nullable) | set if created from a template |
|
|
| `title`, `description`, `department`, `location`, `employment_type`, `requirements`, `salary_range` | — | copied from template at creation time (not a live reference) |
|
|
| `status` | enum | draft / open / closed / filled |
|
|
| `created_at` / `updated_at` | timestamp | |
|
|
| `posted_by` | user ref | |
|
|
|
|
### Application
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `application_id` | UUID | |
|
|
| `position_id` | FK | which job they applied to |
|
|
| `first_name`, `last_name` | string | required |
|
|
| `email` | string | required, validated |
|
|
| `phone` | string | required |
|
|
| `resume_file_url` | string | pointer to stored file |
|
|
| `submitted_at` | timestamp | |
|
|
| `status` | enum | new / reviewed / contacted / rejected / hired |
|
|
| `admin_notes` | text | for admin follow-up tracking |
|
|
|
|
---
|
|
|
|
## 3. Admin Side
|
|
|
|
### 3.1 Create Open Position
|
|
- Form fields: title, description, department, location, employment type, requirements, salary range.
|
|
- Option to start **blank** or **from a template**.
|
|
- On save: generate unique `position_id`, default status `draft` → publish sets `open`.
|
|
|
|
### 3.2 Job Templates (CRUD)
|
|
- List / create / edit / archive templates.
|
|
- **Editing behavior**: support both
|
|
- **Overwrite** current version, or
|
|
- **Save as new version** (keeps history)
|
|
- Version history viewable/revertible.
|
|
- Archiving a template never affects positions already created from it (fields are copied, not linked live).
|
|
|
|
### 3.3 Create Position from Template
|
|
- Admin selects a template → fields pre-fill a new position form → admin edits as needed → save generates a new `position_id`.
|
|
|
|
### 3.4 Review Applications
|
|
- Dashboard listing all submissions, filterable by position, status, or date.
|
|
- Detail view: applicant info, resume preview/download, position applied to.
|
|
- Status + notes fields so admin can track follow-up (contacted, rejected, hired).
|
|
- Nice-to-have: CSV export, direct email link to applicant.
|
|
|
|
---
|
|
|
|
## 4. Client Side
|
|
|
|
### 4.1 Open Positions Listing
|
|
- Public page listing all positions where `status = open`.
|
|
- Card/list view: title, department, location, short summary.
|
|
|
|
### 4.2 Position Detail Page
|
|
- Full description, requirements, etc.
|
|
- "Apply" call-to-action.
|
|
|
|
### 4.3 Application Form
|
|
- Fields: First Name, Last Name, Email, Phone, Resume upload (PDF/DOC).
|
|
- Client-side validation: required fields, email format, file type/size limits.
|
|
- Submits to create an `Application` tied to `position_id`.
|
|
|
|
### 4.4 Confirmation Email
|
|
- Sent automatically to the applicant's email on successful submission.
|
|
- References the position title and sets expectation of review/follow-up.
|
|
- Optional: parallel notification email to admin/HR inbox.
|
|
|
|
---
|
|
|
|
## 5. API Endpoints (suggested)
|
|
|
|
**Admin**
|
|
```
|
|
POST /admin/templates
|
|
PUT /admin/templates/:id
|
|
GET /admin/templates
|
|
|
|
POST /admin/positions (optional template_id)
|
|
PUT /admin/positions/:id
|
|
GET /admin/positions
|
|
|
|
GET /admin/applications (filter by position, status)
|
|
PATCH /admin/applications/:id (update status/notes)
|
|
```
|
|
|
|
**Client (public)**
|
|
```
|
|
GET /positions (open only)
|
|
GET /positions/:id
|
|
POST /positions/:id/apply (multipart form, includes resume)
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Infrastructure Notes
|
|
|
|
- **File storage**: resumes go to object storage (S3 or equivalent); store the file URL/key on the Application record, not the binary in the database.
|
|
- **Email**: use a transactional email provider (SendGrid, SES, Postmark) for confirmation + admin notification emails, driven by templates.
|
|
- **Auth**: admin routes require authenticated/role-gated access; client routes remain public.
|
|
|
|
---
|
|
|
|
## 7. Database Migrations
|
|
|
|
Assumes PostgreSQL syntax (adjust types for MySQL/SQLite as needed). Each migration is additive and ordered so foreign keys resolve correctly.
|
|
|
|
### Migration 001 — create `job_templates`
|
|
```sql
|
|
-- up
|
|
CREATE TABLE job_templates (
|
|
template_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
title VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
department VARCHAR(255),
|
|
location VARCHAR(255),
|
|
employment_type VARCHAR(50),
|
|
requirements TEXT,
|
|
salary_range VARCHAR(100),
|
|
version INT NOT NULL DEFAULT 1,
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
created_by UUID REFERENCES users(id),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX idx_job_templates_active ON job_templates (is_active);
|
|
|
|
-- down
|
|
DROP TABLE IF EXISTS job_templates;
|
|
```
|
|
|
|
### Migration 002 — create `job_template_versions` (version history)
|
|
```sql
|
|
-- up
|
|
CREATE TABLE job_template_versions (
|
|
version_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
template_id UUID NOT NULL REFERENCES job_templates(template_id) ON DELETE CASCADE,
|
|
version INT NOT NULL,
|
|
title VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
department VARCHAR(255),
|
|
location VARCHAR(255),
|
|
employment_type VARCHAR(50),
|
|
requirements TEXT,
|
|
salary_range VARCHAR(100),
|
|
saved_by UUID REFERENCES users(id),
|
|
saved_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX idx_template_versions_template_id ON job_template_versions (template_id);
|
|
|
|
-- down
|
|
DROP TABLE IF EXISTS job_template_versions;
|
|
```
|
|
|
|
### Migration 003 — create `job_positions`
|
|
```sql
|
|
-- up
|
|
CREATE TYPE position_status AS ENUM ('draft', 'open', 'closed', 'filled');
|
|
|
|
CREATE TABLE job_positions (
|
|
position_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
template_id UUID REFERENCES job_templates(template_id) ON DELETE SET NULL,
|
|
title VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
department VARCHAR(255),
|
|
location VARCHAR(255),
|
|
employment_type VARCHAR(50),
|
|
requirements TEXT,
|
|
salary_range VARCHAR(100),
|
|
status position_status NOT NULL DEFAULT 'draft',
|
|
posted_by UUID REFERENCES users(id),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX idx_job_positions_status ON job_positions (status);
|
|
|
|
-- down
|
|
DROP TABLE IF EXISTS job_positions;
|
|
DROP TYPE IF EXISTS position_status;
|
|
```
|
|
|
|
### Migration 004 — create `applications`
|
|
```sql
|
|
-- up
|
|
CREATE TYPE application_status AS ENUM ('new', 'reviewed', 'contacted', 'rejected', 'hired');
|
|
|
|
CREATE TABLE applications (
|
|
application_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
position_id UUID NOT NULL REFERENCES job_positions(position_id) ON DELETE CASCADE,
|
|
first_name VARCHAR(100) NOT NULL,
|
|
last_name VARCHAR(100) NOT NULL,
|
|
email VARCHAR(255) NOT NULL,
|
|
phone VARCHAR(30) NOT NULL,
|
|
resume_file_url TEXT NOT NULL,
|
|
status application_status NOT NULL DEFAULT 'new',
|
|
admin_notes TEXT,
|
|
submitted_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX idx_applications_position_id ON applications (position_id);
|
|
CREATE INDEX idx_applications_status ON applications (status);
|
|
CREATE INDEX idx_applications_email ON applications (email);
|
|
|
|
-- down
|
|
DROP TABLE IF EXISTS applications;
|
|
DROP TYPE IF EXISTS application_status;
|
|
```
|
|
|
|
### Migration 005 — updated_at auto-touch triggers (optional, Postgres)
|
|
```sql
|
|
-- up
|
|
CREATE OR REPLACE FUNCTION set_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = now();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trg_job_templates_updated_at
|
|
BEFORE UPDATE ON job_templates
|
|
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
|
|
|
CREATE TRIGGER trg_job_positions_updated_at
|
|
BEFORE UPDATE ON job_positions
|
|
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
|
|
|
-- down
|
|
DROP TRIGGER IF EXISTS trg_job_templates_updated_at ON job_templates;
|
|
DROP TRIGGER IF EXISTS trg_job_positions_updated_at ON job_positions;
|
|
DROP FUNCTION IF EXISTS set_updated_at();
|
|
```
|
|
|
|
### Migration order & notes
|
|
1. `job_templates` → 2. `job_template_versions` → 3. `job_positions` → 4. `applications` → 5. triggers.
|
|
2. `users` table is assumed to already exist (for `created_by` / `posted_by`); drop those FK constraints if no auth/user table exists yet.
|
|
3. Run each migration's `up` in order; `down` scripts reverse in the opposite order for rollback.
|
|
4. If using a migration tool (Knex, Prisma, Sequelize, Alembic, Rails ActiveRecord, etc.), split each numbered migration above into that tool's file format/naming convention — the SQL logic stays the same.
|
|
|
|
---
|
|
|
|
## 8. Build Phases
|
|
|
|
| Phase | Scope |
|
|
|---|---|
|
|
| 1 | Data models + admin CRUD for Positions (no templates yet) |
|
|
| 2 | Template CRUD + versioning + "create position from template" |
|
|
| 3 | Client-facing listing + detail pages |
|
|
| 4 | Application form + file upload + submission handling |
|
|
| 5 | Confirmation email + admin notification email |
|
|
| 6 | Admin review dashboard (applications list, status, notes) |
|
|
| 7 | Polish: validation, admin auth/permissions, testing |
|
|
|
|
---
|
|
|
|
## 9. Open Questions
|
|
|
|
- How is admin access authenticated/restricted (login system, roles)?
|
|
- Should duplicate applications (same email + position) be blocked or allowed?
|
|
- What resume file types/size limits are acceptable?
|
|
- Should closed positions remain visible (marked "closed") or disappear from the client list entirely? |