Case Study
Inventory Management System
An enterprise military-grade inventory tracking platform for managing item nomenclatures, stock ledgers, voucher-based receipt/issue workflows, condemnation board proceedings, and a dual-role dashboard for HQ Master and Establishment units.
Defence-Grade Inventory Tracking
This system was built for a military establishment unit hierarchy. It enforces strict Master HQ / Establishment role separation, atomic MongoDB transactions for stock balance consistency, and real-time Socket.IO push events for ledger updates and condemnation approvals.
Core Feature Overview
📦
Stock Ledger Engine
Every stock movement (receipt, issue, condemnation, seeding) is recorded as an immutable StockLedger transaction with opening/closing balance tracking per item per establishment.
Complete Project Structure
root
Inventory management system/
backend/
src/
config/
db.js— Mongoose Atlas connection
passport.js— Passport.js JWT strategy
redis.js— Redis client (session caching)
socket.js— Socket.IO initialization
controllers/
analytics.controller.js— Role-aware dashboard KPIs
auth.controller.js— Login, refresh token, register
condemnation.controller.js— Board creation, approval, disposal
item.controller.js— Nomenclature CRUD with ledger folio
notification.controller.js— In-app notification management
report.controller.js— Excel export generation
user.controller.js— Establishment profile management
voucher.controller.js— Receipt/Issue voucher lifecycle
models/
Item.model.js— Item nomenclature with ledger folio
StockLedger.model.js— Immutable stock transaction records
Voucher.model.js— Receipt/Issue vouchers with status machine
Condemnation.model.js— Condemnation board proceedings
User.model.js— Master HQ and Establishment accounts
Notification.model.js— Real-time notification feed
ActivityLog.model.js— Security audit trail
RefreshToken.model.js— JWT refresh token store
middleware/
auth.middleware.js— Bearer JWT verification
rbac.middleware.js— Master / Establishment role guard
upload.middleware.js— Multer for voucher scan attachments
validate.middleware.js— Joi schema validation
rateLimiter.middleware.js— API rate limiting
socket/
index.js— JWT-auth + room join logic
handlers/
notification.handler.js— Push notification to user rooms
order.handler.js— Voucher status push events
product.handler.js— Stock level change push events
services/
auth.service.js— JWT access/refresh pair management
email.service.js— Nodemailer transports
seeders/
dbSeeder.js— Initial nomenclature and HQ user seeding
utils/
constants.js— VOUCHER_TYPES, VOUCHER_STATUS, ITEM_UNITS
ApiError.js— Standardized error class
ApiResponse.js— Standardized response wrapper
helpers.js— createActivityLog, balance helpers
server.js
frontend/
src/
features/— Redux Toolkit slices (items, vouchers, stock)
store/— RTK store + RTK Query API definitions
Stock Movement Lifecycle
1
Item Nomenclature Registration
Master HQ registers item nomenclatures with `ledgerFolio` numbers (the official military ledger reference IDs), unit of issue (Nos/Kg/Ltrs/Mtrs/Pairs/Sets/Rolls), and minimum stock level thresholds for low-stock alerting.
2
Initial Stock Seeding
The `dbSeeder.js` seeds the initial stock positions per establishment using `transactionType: 'seeding'` ledger entries. Each seed entry records `openingBalance: 0`, `quantity: initialQty`, and `closingBalance: initialQty`.
3
Voucher Submission
An Establishment unit submits a Receipt or Issue voucher with `voucherNo`, origin/destination, and an items array. The voucher is created with `status: 'pending'` and triggers a Socket.IO event to the Master HQ room.
4
HQ Voucher Approval
Master HQ reviews and approves the voucher. The controller runs a MongoDB transaction: for each voucher item, it reads the latest `StockLedger` balance, computes the new closing balance, creates a new `StockLedger` entry, and advances the voucher status to `approved`.
5
Condemnation Board
Establishments initiate condemnation boards for damaged/unserviceable items. The board lists presiding officer, board members, and condemned items with quantities. Stock availability is validated before allowing submission.
6
Condemnation Approval & Write-Off
Master HQ approves the condemnation board inside a MongoDB session transaction. Each condemned item's current balance is decremented in the `StockLedger` with `transactionType: 'condemnation'`. The board status advances to `approved` and disposal status is set.
MongoDB Data Models
StockLedger — Immutable Transaction Records
javascript
// backend/src/models/StockLedger.model.js
import mongoose from 'mongoose';
const stockLedgerSchema = new mongoose.Schema({
item: { type: mongoose.Schema.Types.ObjectId, ref: 'Item', required: true },
establishment: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
openingBalance: { type: Number, required: true, min: 0 },
quantity: { type: Number, required: true }, // + for receipts, - for issues/condemnation
closingBalance: { type: Number, required: true, min: 0 },
transactionType: {
type: String, required: true,
enum: ['receipt', 'issue', 'condemnation', 'seeding']
},
voucherId: { type: mongoose.Schema.Types.ObjectId, ref: 'Voucher' },
remarks: { type: String, trim: true },
}, { timestamps: true });
stockLedgerSchema.index({ establishment: 1, item: 1 });
stockLedgerSchema.index({ createdAt: -1 });Voucher Model — Receipt/Issue State Machine
javascript
// backend/src/models/Voucher.model.js
const voucherSchema = new mongoose.Schema({
voucherNo: { type: String, required: true, unique: true, trim: true },
type: { type: String, required: true, enum: Object.values(VOUCHER_TYPES) }, // 'receipt' | 'issue'
date: { type: Date, required: true, default: Date.now },
origin: { type: String, required: true, trim: true }, // Source unit/depot
destination: { type: String, required: true, trim: true }, // Receiving unit
items: [{
item: { type: mongoose.Schema.Types.ObjectId, ref: 'Item', required: true },
qty: { type: Number, required: true, min: 1 },
remarks: String,
_id: false,
}],
status: { type: String, enum: Object.values(VOUCHER_STATUS), default: 'pending' },
submittedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
approvedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
approvedAt: { type: Date },
attachment: { type: String }, // Local file path for scanned voucher copy
remarks: { type: String, trim: true },
}, { timestamps: true });Condemnation Board — Atomic Approval Transaction
javascript
// backend/src/controllers/condemnation.controller.js
export const approveCondemnationBoard = asyncHandler(async (req, res) => {
if (req.user.role !== USER_ROLES.MASTER)
throw ApiError.forbidden('Only Master HQ can approve condemnation boards');
const board = await Condemnation.findById(req.params.id);
if (!board) throw ApiError.notFound('Condemnation board record not found');
if (board.status !== 'pending') throw ApiError.badRequest(`Board status is already "${board.status}"`);
const session = await mongoose.startSession();
session.startTransaction();
try {
// Apply ledger decrements for each condemned item inside the transaction
for (const itemObj of board.items) {
const lastTx = await StockLedger.findOne({
item: itemObj.item, establishment: board.establishment
}).sort({ createdAt: -1 });
const currentBal = lastTx ? lastTx.closingBalance : 0;
const newBal = currentBal - itemObj.qty;
if (newBal < 0) throw new Error(`Insufficient stock for condemned item: ${itemObj.item}`);
await StockLedger.create([{
item: itemObj.item, establishment: board.establishment,
openingBalance: currentBal, quantity: -itemObj.qty, closingBalance: newBal,
transactionType: 'condemnation',
remarks: `Condemnation Board: ${board.boardNo}`,
}], { session });
}
board.status = 'approved';
board.disposalStatus = 'Approved for Disposal';
board.approvedBy = req.user._id;
await board.save({ session });
await session.commitTransaction();
// Notify via Socket.IO
const io = getIO();
if (io) {
io.emit(SOCKET_EVENTS.CONDEMNATION_APPROVED, { boardId: board._id, boardNo: board.boardNo });
io.emit(SOCKET_EVENTS.LEDGER_UPDATED, { establishmentId: board.establishment });
}
res.status(200).json(ApiResponse.success(board, 'Condemnation approved and items written-off from stock register'));
} catch (err) {
await session.abortTransaction();
throw ApiError.internal(`Transaction failed: ${err.message}`);
} finally {
session.endSession();
}
});Role-Aware Dashboard Analytics
javascript
// backend/src/controllers/analytics.controller.js
export const getDashboardStats = asyncHandler(async (req, res) => {
const { role, _id } = req.user;
if (role === USER_ROLES.MASTER) {
// HQ sees cross-establishment aggregates
const nomenclatureCount = await Item.countDocuments({ isActive: true });
const establishmentCount = await User.countDocuments({ role: 'establishment', isActive: true });
const pendingVouchers = await Voucher.countDocuments({ status: 'pending' });
const pendingCondemnations = await Condemnation.countDocuments({ status: 'pending' });
return res.json(ApiResponse.success({ summary: { nomenclatureCount, establishmentCount, pendingVouchers, pendingCondemnations } }));
}
// Establishment sees own unit stats
const pendingVouchers = await Voucher.countDocuments({ submittedBy: _id, status: 'pending' });
// Aggregate latest balances per item via running ledger
const ledgerItems = await StockLedger.aggregate([
{ $match: { establishment: _id } },
{ $sort: { createdAt: -1 } },
{ $group: { _id: '$item', lastBalance: { $first: '$closingBalance' } } },
{ $match: { lastBalance: { $gt: 0 } } },
]);
// Count items below minimum stock threshold
let lowStockCount = 0;
for (const { _id: itemId } of ledgerItems) {
const item = await Item.findById(itemId);
const lastTx = await StockLedger.findOne({ item: itemId, establishment: _id }).sort({ createdAt: -1 });
if (lastTx && item && lastTx.closingBalance < item.minStockLevel) lowStockCount++;
}
res.json(ApiResponse.success({ pendingVouchers, stockItemsCount: ledgerItems.length, lowStockCount }));
});Transaction Integrity
All stock-mutating operations (voucher approval, condemnation write-off) use `mongoose.startSession()` + `session.startTransaction()` to guarantee atomicity. If any part of the multi-item loop fails, the entire transaction is rolled back — ensuring ledger balances always remain consistent.