Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import assetRoutes from './routes/assetRoutes.js';
import paymentRoutes from './routes/paymentRoutes.js';
import searchRoutes from './routes/searchRoutes.js';
import contractRoutes from './routes/contractRoutes.js';
import scheduleRoutes from './routes/scheduleRoutes.js';
import ratesRoutes from './routes/ratesRoutes.js';
import stellarThrottlingRoutes from './routes/stellarThrottlingRoutes.js';
import { dataRateLimit } from './middlewares/rateLimitMiddleware.js';
Expand Down Expand Up @@ -73,6 +74,14 @@ app.use('/api/v1', apiRateLimit(), v1Routes);
app.use('/webhooks', apiRateLimit(), webhookRoutes);

// Upstream / Base routes
app.use('/api/auth', authRoutes);
app.use('/api/payroll', payrollRoutes);
app.use('/api/employees', employeeRoutes);
app.use('/api/assets', assetRoutes);
app.use('/api/payments', paymentRoutes);
app.use('/api/search', searchRoutes);
app.use('/api', contractRoutes);
app.use('/api/schedules', scheduleRoutes);
app.use('/api/auth', authRateLimit(), authRoutes);
app.use('/api/payroll', apiRateLimit(), payrollRoutes);
app.use('/api/employees', dataRateLimit(), employeeRoutes);
Expand Down
63 changes: 63 additions & 0 deletions backend/src/controllers/scheduleController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Request, Response } from 'express';
import { ScheduleService } from '../services/scheduleService.js';
import logger from '../utils/logger.js';

export class ScheduleController {
/**
* GET /api/schedules
*/
static async listSchedules(req: Request, res: Response) {
const orgId = Number(req.headers['x-organization-id']);
if (!orgId) return res.status(400).json({ error: 'Missing organization context' });

try {
const schedules = await ScheduleService.listSchedules(orgId);
res.json(schedules);
} catch (error: any) {
logger.error('Error fetching schedules', error);
res.status(500).json({ error: error.message });
}
}

/**
* POST /api/schedules
*/
static async saveSchedule(req: Request, res: Response) {
const orgId = Number(req.headers['x-organization-id']);
if (!orgId) return res.status(400).json({ error: 'Missing organization context' });

const config = req.body; // SchedulingConfig
if (!config || !config.frequency || !config.timeOfDay) {
return res.status(400).json({ error: 'Invalid schedule config' });
}

try {
const schedule = await ScheduleService.saveSchedule(orgId, config);
res.json(schedule);
} catch (error: any) {
logger.error('Error saving schedule', error);
res.status(500).json({ error: error.message });
}
}

/**
* DELETE /api/schedules/:id
*/
static async cancelSchedule(req: Request, res: Response) {
const { id } = req.params;
const orgId = Number(req.headers['x-organization-id']);
if (!orgId) return res.status(400).json({ error: 'Missing organization context' });

try {
const success = await ScheduleService.cancelSchedule(Number(id), orgId);
if (success) {
res.json({ message: 'Schedule cancelled successfully' });
} else {
res.status(404).json({ error: 'Schedule not found for this organization' });
}
} catch (error: any) {
logger.error('Error cancelling schedule', error);
res.status(500).json({ error: error.message });
}
}
}
4 changes: 4 additions & 0 deletions backend/src/db/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ import path from 'path';

import dotenv from 'dotenv';
import { Pool, PoolClient } from 'pg';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// ─── Bootstrap ──────────────────────────────────────────────────────────────

Expand Down
21 changes: 21 additions & 0 deletions backend/src/db/migrations/016_create_payroll_schedules.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-- Create payroll_schedules table
CREATE TABLE IF NOT EXISTS payroll_schedules (
id SERIAL PRIMARY KEY,
organization_id INTEGER NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
frequency VARCHAR(20) NOT NULL CHECK (frequency IN ('weekly', 'biweekly', 'monthly')),
day_of_week INTEGER CHECK (day_of_week BETWEEN 0 AND 6),
day_of_month INTEGER CHECK (day_of_month BETWEEN 1 AND 31),
time_of_day TIME NOT NULL,
config JSONB NOT NULL, -- Stores the preferences/employee list
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'paused', 'cancelled')),
last_run_at TIMESTAMP,
next_run_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_payroll_schedules_org_id ON payroll_schedules(organization_id);
CREATE INDEX idx_payroll_schedules_next_run ON payroll_schedules(next_run_at) WHERE status = 'active';

CREATE TRIGGER update_payroll_schedules_updated_at BEFORE UPDATE ON payroll_schedules
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
9 changes: 9 additions & 0 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import app from './app.js';
import logger from './utils/logger.js';
import config from './config/index.js';
import { initializeSocket } from './services/socketService.js';
<<<<<<< feature/payroll-scheduler
import { ScheduleService } from './services/scheduleService.js';
=======
import { startWorkers } from './workers/index.js';
>>>>>>> main

dotenv.config();

Expand All @@ -13,8 +17,13 @@ const server = createServer(app);
// Initialize Socket.IO
initializeSocket(server);

<<<<<<< feature/payroll-scheduler
// Initialize Scheduler
ScheduleService.init();
=======
// Start BullMQ Background Workers
startWorkers();
>>>>>>> main

const PORT = config.port || process.env.PORT || 4000;

Expand Down
15 changes: 15 additions & 0 deletions backend/src/routes/scheduleRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Router } from 'express';
import { ScheduleController } from '../controllers/scheduleController.js';
import { authenticateJWT } from '../middlewares/auth.js';
import { isolateOrganization } from '../middlewares/rbac.js';

const router = Router();

router.use(authenticateJWT);
router.use(isolateOrganization);

router.get('/', ScheduleController.listSchedules);
router.post('/', ScheduleController.saveSchedule);
router.delete('/:id', ScheduleController.cancelSchedule);

export default router;
Loading
Loading