Skip to content

Commit 348bc55

Browse files
committed
feat: add billing transaction and staff CRUD operations
1 parent 53bfac5 commit 348bc55

6 files changed

Lines changed: 349 additions & 2 deletions

File tree

src/app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import adminRoutes from './routes/adminroutes.js';
1212

1313
import doctorRoutes from './routes/doctor.routes.js';
1414
import receptionRoutes from './routes/reception.routes.js';
15+
import billingRoutes from './routes/billing.routes.js';
1516

1617
import patientRoutes from './routes/patient.js';
1718
import deptRoutes from './routes/dept.js';
@@ -56,6 +57,7 @@ app.use('/api/admin', adminRoutes);
5657

5758
app.use('/api/users/doctors', doctorRoutes);
5859
app.use('/api/users/receptionist', receptionRoutes);
60+
app.use('/api/billing', billingRoutes);
5961

6062
app.use('/api/patients', patientRoutes);
6163
app.use('/api/departments', deptRoutes);
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import Billing from '../models/billing.js';
2+
import billingService from '../services/billing.service.js';
3+
4+
const createBilling = async (req, res, next) => {
5+
try {
6+
const { patient, appointment, amount } = req.body;
7+
8+
if (!patient || !amount) {
9+
return res.status(400).json({
10+
success: false,
11+
message: "Patient and amount are required",
12+
});
13+
}
14+
15+
const billing = await Billing.create({
16+
patient,
17+
appointment,
18+
amount,
19+
createdBy: req.user.id,
20+
});
21+
22+
res.status(201).json({
23+
success: true,
24+
data: billing,
25+
});
26+
} catch (error) {
27+
next(error);
28+
}
29+
};
30+
31+
const getAllBilling = async (req, res, next) => {
32+
try {
33+
const page = parseInt(req.query.page) || 1;
34+
const limit = parseInt(req.query.limit) || 10;
35+
const skip = (page - 1) * limit;
36+
37+
const bills = await Billing.find()
38+
.populate("patient", "name email")
39+
.sort({ createdAt: -1 })
40+
.skip(skip)
41+
.limit(limit);
42+
43+
const total = await Billing.countDocuments();
44+
45+
res.status(200).json({
46+
success: true,
47+
data: {
48+
bills,
49+
total,
50+
page,
51+
pages: Math.ceil(total / limit),
52+
},
53+
});
54+
} catch (error) {
55+
next(error);
56+
}
57+
};
58+
59+
const updateBillingStatus = async (req, res, next) => {
60+
try {
61+
const { status, paymentMethod, transactionId } = req.body;
62+
63+
const bill = await Billing.findById(req.params.id);
64+
65+
if (!bill) {
66+
return res.status(404).json({
67+
success: false,
68+
message: "Billing record not found",
69+
});
70+
}
71+
72+
if (!["pending", "paid"].includes(status)) {
73+
return res.status(400).json({
74+
success: false,
75+
message: "Invalid status value",
76+
});
77+
}
78+
79+
if (bill.status === "paid" && status === "pending") {
80+
return res.status(400).json({
81+
success: false,
82+
message: "Cannot revert paid billing to pending",
83+
});
84+
}
85+
86+
bill.status = status;
87+
88+
if (status === "paid") {
89+
bill.paymentMethod = paymentMethod;
90+
bill.transactionId = transactionId;
91+
bill.paidAt = new Date();
92+
}
93+
94+
await bill.save();
95+
96+
res.status(200).json({
97+
success: true,
98+
data: bill,
99+
});
100+
} catch (error) {
101+
next(error);
102+
}
103+
};
104+
105+
const deleteBilling = async (req, res, next) => {
106+
try {
107+
const bill = await Billing.findById(req.params.id);
108+
109+
if (!bill) {
110+
return res.status(404).json({
111+
success: false,
112+
message: "Billing record not found",
113+
});
114+
}
115+
116+
await bill.deleteOne();
117+
118+
res.status(200).json({
119+
success: true,
120+
message: "Billing deleted",
121+
});
122+
} catch (error) {
123+
next(error);
124+
}
125+
};
126+
127+
// Staff CRUD for admin
128+
129+
const createBillingStaff = async (req, res, next) => {
130+
try {
131+
const billing = await billingService.createBillingStaff(req.body);
132+
res.status(201).json(billing);
133+
} catch (error) {
134+
next(error)
135+
}
136+
};
137+
138+
const changePassword = async (req, res, next) => {
139+
try {
140+
const { oldPassword, newPassword } = req.body;
141+
const result = await billingService.changePassword(
142+
req.user.id,
143+
oldPassword,
144+
newPassword);
145+
res.status(200).json(result);
146+
} catch (err) {
147+
next(err)
148+
}
149+
};
150+
151+
const getAllBillingStaff = async (req, res, next) => {
152+
try {
153+
const billing = await billingService.getAllBillingStaff();
154+
res.json(billing);
155+
} catch (err) {
156+
next(err)
157+
}
158+
};
159+
160+
const updateBillingStaff = async (req, res, next) => {
161+
try {
162+
const { id } = req.params;
163+
const billing = await billingService.updateBillingStaff(id, req.body);
164+
res.json(billing);
165+
} catch (err) {
166+
next(err)
167+
}
168+
};
169+
170+
const deleteBillingStaff = async (req, res, next) => {
171+
try {
172+
const { id } = req.params;
173+
const result = await billingService.deleteBillingStaff(id);
174+
res.json(result);
175+
} catch (err) {
176+
next(err)
177+
}
178+
};
179+
180+
export {
181+
// Billing operations
182+
createBilling,
183+
getAllBilling,
184+
updateBillingStatus,
185+
deleteBilling,
186+
187+
// Staff CRUD for admin
188+
189+
createBillingStaff,
190+
changePassword,
191+
getAllBillingStaff,
192+
updateBillingStaff,
193+
deleteBillingStaff
194+
}

src/models/User.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const userSchema = new mongoose.Schema({
1212
role: {
1313
type: String,
1414
required: true,
15-
enum: ['admin', 'doctor','receptionist']
15+
enum: ['admin', 'doctor','receptionist','billing']
1616
},
1717

1818

src/models/billing.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,9 @@ const billingSchema = new mongoose.Schema(
4545
{ timestamps: true }
4646
);
4747

48-
export default mongoose.model("Billing", billingSchema);
48+
billingSchema.index({ status: 1 });
49+
billingSchema.index({ patient: 1 });
50+
billingSchema.index({ createdAt: -1 });
51+
billingSchema.index({ status: 1, createdAt: -1 });
52+
53+
export default mongoose.model("Billing", billingSchema);

src/routes/billing.routes.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import express from 'express';
2+
import { protect, authorize } from '../middleware/authmiddleware.js';
3+
import * as billingController from '../controllers/billing.controller.js';
4+
5+
const router = express.Router();
6+
7+
//BILLING TRANSACTIONS
8+
9+
router.post(
10+
"/",
11+
protect,
12+
authorize(["admin"]),
13+
billingController.createBilling
14+
);
15+
16+
router.get(
17+
"/",
18+
protect,
19+
authorize(["admin", "billing"]),
20+
billingController.getAllBilling
21+
);
22+
23+
router.patch(
24+
"/:id/status",
25+
protect,
26+
authorize(["admin", "billing"]),
27+
billingController.updateBillingStatus
28+
);
29+
30+
router.delete(
31+
"/:id",
32+
protect,
33+
authorize(["admin"]),
34+
billingController.deleteBilling
35+
);
36+
37+
38+
//BILLING STAFF MANAGEMENT
39+
40+
41+
router.post(
42+
"/staff",
43+
protect,
44+
authorize(["admin"]),
45+
billingController.createBillingStaff
46+
);
47+
48+
router.get(
49+
"/staff",
50+
protect,
51+
authorize(["admin"]),
52+
billingController.getAllBillingStaff
53+
);
54+
55+
router.put(
56+
"/staff/:id",
57+
protect,
58+
authorize(["admin"]),
59+
billingController.updateBillingStaff
60+
);
61+
62+
router.delete(
63+
"/staff/:id",
64+
protect,
65+
authorize(["admin"]),
66+
billingController.deleteBillingStaff
67+
);
68+
69+
router.put(
70+
"/staff/change-password",
71+
protect,
72+
authorize(["billing"]),
73+
billingController.changePassword
74+
);
75+
76+
export default router;

src/services/billing.service.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import billing from '../models/billing.js';
2+
import User from '../models/User.js';
3+
import bcrypt from 'bcrypt';
4+
5+
const createBillingStaff = async (data) => {
6+
const password = data.password || 'billing@123';
7+
const hashedPassword = await bcrypt.hash(password, 10);
8+
9+
const billing = new User({
10+
...data,
11+
password: hashedPassword,
12+
role: 'billing',
13+
});
14+
15+
await billing.save();
16+
17+
return {
18+
success: true,
19+
message: 'Billing staff created successfully',
20+
};
21+
};
22+
23+
24+
const changePassword = async (userId, oldPassword, newPassword) => {
25+
const billing = await User.findById(userId);
26+
if(!billing) throw new Error('Billing staff not found');
27+
28+
const isMatch = await bcrypt.compare(oldPassword, billing.password);
29+
if(!isMatch) throw new Error('Old password is incorrect');
30+
31+
billing.password = await bcrypt.hash(newPassword, 10);
32+
await billing.save();
33+
34+
return { message: 'Password changed successfully' };
35+
};
36+
37+
const getAllBillingStaff = async () => {
38+
return await User.find({ role: 'billing' });
39+
};
40+
41+
const updateBillingStaff = async (id, data) => {
42+
if(data.password) {
43+
data.password = await bcrypt.hash(data.password, 10);
44+
}
45+
46+
const billing = await User.findOneAndUpdate(
47+
{ _id: id, role: 'billing' },
48+
data,
49+
{ new: true, runvalidators: true }
50+
);
51+
52+
if(!billing) throw new Error('Billing staff not found');
53+
54+
return billing;
55+
};
56+
57+
const deleteBillingStaff = async (id) => {
58+
const billing = await User.findOneAndDelete({ _id: id, role: 'billing' });
59+
if(!billing) throw new Error('Billing staff not found');
60+
61+
return { message: 'Billing staff deleted successfully' };
62+
};
63+
64+
export default{
65+
createBillingStaff,
66+
changePassword,
67+
getAllBillingStaff,
68+
updateBillingStaff,
69+
deleteBillingStaff
70+
}

0 commit comments

Comments
 (0)