Skip to content
Merged
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
3 changes: 0 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import healthRoutes from './src/routes/health.js';
import { setupSocket } from './src/socket/socket.js';
import issueRoutes from './src/routes/issues.js';
import userRoutes from './src/routes/users.js';
import branchRoutes from './src/routes/branch.js';
import thirdPartiesRoutes from './src/routes/thirdparties.js';
import cashRequestRoutes from './src/routes/cashRequestRoutes.js';

Expand All @@ -30,6 +31,7 @@ app.use('/api/v1/cash-requests', cashRequestRoutes);

app.use('/api/v1/issues', issueRoutes);
app.use('/api/v1/users', userRoutes);
app.use('/api/v1/branches', branchRoutes);
app.use('/api/v1/thirdparties', thirdPartiesRoutes);

// Basic route
Expand Down
141 changes: 141 additions & 0 deletions src/__tests__/branch.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// tests/branch.test.js
import request from 'supertest';
import express from 'express';
import { getSequelizeInstance } from '../services/connectionService.js';
import branchRoutes from '../routes/branch.js';
import Branch from '../models/branch.js';

const app = express();
app.use(express.json());
app.use('/api/v1/branches', branchRoutes);

let sequelize;

beforeAll(async () => {
sequelize = getSequelizeInstance();
await sequelize.sync({ force: true });
});

afterAll(async () => {
await sequelize.close();
});

describe('Branch Endpoints', () => {
let branchId;

describe('POST /api/v1/branches', () => {
it('should create a new branch', async () => {
const res = await request(app).post('/api/v1/branches').send({
name: 'Colombo Branch',
location: 'Colombo 07',
manager_id: 10,
});

expect(res.statusCode).toBe(201);
expect(res.body.success).toBe(true);
expect(res.body.data).toHaveProperty('id');
expect(res.body.data.name).toBe('Colombo Branch');
expect(res.body.data.location).toBe('Colombo 07');
expect(res.body.data.manager_id).toBe(10);

branchId = res.body.data.id;
});

it('should reject branch with missing name', async () => {
const res = await request(app).post('/api/v1/branches').send({
location: 'Colombo 07',
});

expect(res.statusCode).toBe(400);
expect(res.body.success).toBe(false);
expect(res.body.message).toBe('Name and location are required');
});

it('should reject branch with missing location', async () => {
const res = await request(app).post('/api/v1/branches').send({
name: 'Test Branch',
});

expect(res.statusCode).toBe(400);
expect(res.body.success).toBe(false);
expect(res.body.message).toBe('Name and location are required');
});
});

describe('GET /api/v1/branches', () => {
it('should get all branches', async () => {
const res = await request(app).get('/api/v1/branches');

expect(res.statusCode).toBe(200);
expect(res.body.success).toBe(true);
expect(Array.isArray(res.body.data)).toBe(true);
expect(res.body.data.length).toBeGreaterThan(0);
});
});

describe('GET /api/v1/branches/:id', () => {
it('should get branch by valid ID', async () => {
const res = await request(app).get(`/api/v1/branches/${branchId}`);

expect(res.statusCode).toBe(200);
expect(res.body.success).toBe(true);
expect(res.body.data.id).toBe(branchId);
expect(res.body.data.name).toBe('Colombo Branch');
});

it('should return 404 for non-existent branch ID', async () => {
const res = await request(app).get('/api/v1/branches/9999');

expect(res.statusCode).toBe(404);
expect(res.body.success).toBe(false);
});
});

describe('PUT /api/v1/branches/:id', () => {
it('should update branch name and manager_id', async () => {
const res = await request(app).put(`/api/v1/branches/${branchId}`).send({
name: 'Colombo Updated',
manager_id: 20
});

expect(res.statusCode).toBe(200);
expect(res.body.success).toBe(true);
expect(res.body.data.name).toBe('Colombo Updated');
expect(res.body.data.manager_id).toBe(20);
expect(res.body.data.location).toBe('Colombo 07');
});

it('should return 404 for non-existent branch update', async () => {
const res = await request(app).put('/api/v1/branches/9999').send({
name: 'No Branch'
});

expect(res.statusCode).toBe(404);
expect(res.body.success).toBe(false);
});
});

describe('DELETE /api/v1/branches/:id', () => {
it('should delete branch', async () => {
const res = await request(app).delete(`/api/v1/branches/${branchId}`);

expect(res.statusCode).toBe(200);
expect(res.body.success).toBe(true);
expect(res.body.message).toBe('Branch deleted successfully');
});

it('should not find deleted branch', async () => {
const res = await request(app).get(`/api/v1/branches/${branchId}`);

expect(res.statusCode).toBe(404);
expect(res.body.success).toBe(false);
});

it('should return 404 when deleting non-existent branch', async () => {
const res = await request(app).delete('/api/v1/branches/9999');

expect(res.statusCode).toBe(404);
expect(res.body.success).toBe(false);
});
});
});
127 changes: 127 additions & 0 deletions src/controllers/branchController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import branchService from '../services/branchService.js';

class BranchController {
// POST /api/v1/branches - Create a new branch
async addBranch(req, res) {
try {
const { name, location, manager_id } = req.body; // Removed contactNumber

// Validation
if (!name || !location) {
return res.status(400).json({
success: false,
message: 'Name and location are required',
});
}

const result = await branchService.addBranch({
name,
location,
manager_id
// contactNumber removed
});

if (result.success) {
return res.status(201).json(result);
} else {
return res.status(400).json(result);
}
} catch (error) {
return res.status(500).json({
success: false,
message: 'Internal server error',
error: error.message,
});
}
}

// GET /api/v1/branches - Get all branches
async getAllBranches(req, res) {
try {
const result = await branchService.getAllBranches();

if (result.success) {
return res.status(200).json(result);
} else {
return res.status(404).json(result);
}
} catch (error) {
return res.status(500).json({
success: false,
message: 'Internal server error',
error: error.message,
});
}
}

// GET /api/v1/branches/:id - Get branch by ID
async getBranchById(req, res) {
try {
const { id } = req.params;

const result = await branchService.getBranchById(parseInt(id));

if (result.success) {
return res.status(200).json(result);
} else {
return res.status(404).json(result);
}
} catch (error) {
return res.status(500).json({
success: false,
message: 'Internal server error',
error: error.message,
});
}
}

// PUT /api/v1/branches/:id - Update branch by ID
async updateBranch(req, res) {
try {
const { id } = req.params;
const { name, location, manager_id } = req.body; // Removed contactNumber

const result = await branchService.updateBranch(id, {
name,
location,
manager_id
// contactNumber removed
});

if (result.success) {
return res.status(200).json(result);
} else {
return res.status(404).json(result);
}
} catch (error) {
return res.status(500).json({
success: false,
message: 'Internal server error',
error: error.message,
});
}
}

// DELETE /api/v1/branches/:id - Delete branch by ID
async deleteBranch(req, res) {
try {
const { id } = req.params;

const result = await branchService.deleteBranch(parseInt(id));

if (result.success) {
return res.status(200).json(result);
} else {
return res.status(404).json(result);
}
} catch (error) {
return res.status(500).json({
success: false,
message: 'Internal server error',
error: error.message,
});
}
}
}

export default new BranchController();
8 changes: 4 additions & 4 deletions src/controllers/cashRequestController.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,17 +309,17 @@ export const getTechnicianStats = async (req, res) => {

/**
* Get cash requests by ticket ID
* @route GET /api/v1/cash-requests?ticket_id=<issue_id>
* @query {string} ticket_id - Ticket ID to filter cash requests
* @route GET /api/v1/cash-requests/by-ticket/:ticket_id
* @param {string} ticket_id - Ticket ID to filter cash requests
*/
export const getCashRequestsByTicketId = async (req, res) => {
try {
const { ticket_id } = req.query;
const { ticket_id } = req.params;

if (!ticket_id) {
return res.status(400).json({
success: false,
message: 'ticket_id query parameter is required'
message: 'ticket_id route parameter is required'
});
}

Expand Down
45 changes: 45 additions & 0 deletions src/database/migrations/20251014052343-create-branches-table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use strict';

/** @type {import('sequelize-cli').Migration} */
export default {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('branches', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
name: {
type: Sequelize.STRING,
allowNull: false,
},
location: {
type: Sequelize.STRING,
allowNull: false,
},
manager_id: {
type: Sequelize.INTEGER,
allowNull: true,
},
contactNumber: {
type: Sequelize.STRING,
allowNull: true,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('NOW()'),
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('NOW()'),
},
});
},

async down(queryInterface, Sequelize) {
await queryInterface.dropTable('branches');
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

/** @type {import('sequelize-cli').Migration} */
export default {
async up(queryInterface, Sequelize) {
await queryInterface.removeColumn('branches', 'contactNumber');
},

async down(queryInterface, Sequelize) {
await queryInterface.addColumn('branches', 'contactNumber', {
type: Sequelize.STRING,
allowNull: true,
});
},
};
Loading