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
103 changes: 103 additions & 0 deletions models/BackOrder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
const mongoose = require('mongoose');

/**
* BackOrder Model
* Manages stock-outs and pending orders when inventory is insufficient
*/
const backOrderSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
index: true
},
backOrderId: {
type: String,
unique: true,
required: true
},
stockItemId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'StockItem',
required: true,
index: true
},
sku: {
type: String,
required: true
},
itemName: String,
requestedQuantity: {
type: Number,
required: true
},
fulfilledQuantity: {
type: Number,
default: 0
},
pendingQuantity: {
type: Number,
required: true
},
warehouseId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Warehouse',
required: true
},
requestedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
requestDate: {
type: Date,
default: Date.now
},
expectedFulfillmentDate: Date,
priority: {
type: String,
enum: ['low', 'medium', 'high', 'urgent'],
default: 'medium'
},
status: {
type: String,
enum: ['pending', 'partially_fulfilled', 'fulfilled', 'cancelled'],
default: 'pending'
},
linkedProcurementOrder: {
type: mongoose.Schema.Types.ObjectId,
ref: 'ProcurementOrder'
},
fulfillmentHistory: [{
quantity: Number,
fulfilledDate: Date,
batchNumber: String,
notes: String
}],
cancellationReason: String,
notes: String
}, {
timestamps: true
});

// Pre-save hook to update pending quantity
backOrderSchema.pre('save', function (next) {
this.pendingQuantity = this.requestedQuantity - this.fulfilledQuantity;

// Update status based on fulfillment
if (this.fulfilledQuantity === 0) {
this.status = 'pending';
} else if (this.fulfilledQuantity < this.requestedQuantity) {
this.status = 'partially_fulfilled';
} else if (this.fulfilledQuantity >= this.requestedQuantity) {
this.status = 'fulfilled';
}

next();
});

// Indexes
backOrderSchema.index({ userId: 1, status: 1 });
backOrderSchema.index({ stockItemId: 1, status: 1 });
backOrderSchema.index({ priority: 1, requestDate: 1 });

module.exports = mongoose.model('BackOrder', backOrderSchema);
201 changes: 201 additions & 0 deletions models/StockItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
const mongoose = require('mongoose');

/**
* StockItem Model
* Manages individual stock items with SKU tracking, batch numbers, and expiry dates
*/
const stockMovementSchema = new mongoose.Schema({
movementType: {
type: String,
enum: ['in', 'out', 'transfer', 'adjustment', 'return'],
required: true
},
quantity: {
type: Number,
required: true
},
fromWarehouse: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Warehouse'
},
toWarehouse: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Warehouse'
},
reference: {
type: String
},
referenceType: {
type: String,
enum: ['purchase_order', 'sales_order', 'transfer', 'adjustment', 'return']
},
movementDate: {
type: Date,
default: Date.now
},
performedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
notes: String
}, { _id: false });

const stockItemSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
index: true
},
sku: {
type: String,
required: true,
unique: true,
uppercase: true
},
itemName: {
type: String,
required: true
},
description: String,
category: {
type: String,
required: true
},
subcategory: String,
warehouseId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Warehouse',
required: true,
index: true
},
batchNumber: String,
serialNumber: String,
quantity: {
current: {
type: Number,
required: true,
default: 0
},
reserved: {
type: Number,
default: 0
},
available: {
type: Number,
default: 0
},
unit: {
type: String,
required: true,
default: 'units'
}
},
reorderPoint: {
type: Number,
default: 10
},
safetyStock: {
type: Number,
default: 5
},
maxStockLevel: {
type: Number,
default: 1000
},
pricing: {
costPrice: {
type: Number,
default: 0
},
sellingPrice: {
type: Number,
default: 0
},
currency: {
type: String,
default: 'INR'
}
},
valuation: {
method: {
type: String,
enum: ['FIFO', 'LIFO', 'WAC', 'specific'],
default: 'FIFO'
},
totalValue: {
type: Number,
default: 0
}
},
expiryTracking: {
isPerishable: {
type: Boolean,
default: false
},
expiryDate: Date,
manufacturingDate: Date,
shelfLife: {
value: Number,
unit: {
type: String,
enum: ['days', 'months', 'years']
}
}
},
dimensions: {
length: Number,
width: Number,
height: Number,
weight: Number,
unit: String
},
supplier: {
supplierId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Vendor'
},
supplierName: String,
leadTime: Number
},
stockStatus: {
type: String,
enum: ['in_stock', 'low_stock', 'out_of_stock', 'discontinued'],
default: 'in_stock'
},
movements: [stockMovementSchema],
lastRestocked: Date,
isActive: {
type: Boolean,
default: true
}
}, {
timestamps: true
});

// Pre-save hook to calculate available quantity and stock status
stockItemSchema.pre('save', function (next) {
this.quantity.available = this.quantity.current - this.quantity.reserved;

// Update stock status
if (this.quantity.current === 0) {
this.stockStatus = 'out_of_stock';
} else if (this.quantity.current <= this.reorderPoint) {
this.stockStatus = 'low_stock';
} else {
this.stockStatus = 'in_stock';
}

// Update total value
this.valuation.totalValue = this.quantity.current * this.pricing.costPrice;

next();
});

// Indexes
stockItemSchema.index({ userId: 1, warehouseId: 1 });
stockItemSchema.index({ sku: 1 });
stockItemSchema.index({ category: 1, stockStatus: 1 });
stockItemSchema.index({ 'quantity.current': 1, reorderPoint: 1 });

module.exports = mongoose.model('StockItem', stockItemSchema);
82 changes: 82 additions & 0 deletions models/Warehouse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const mongoose = require('mongoose');

/**
* Warehouse Model
* Manages multiple warehouse/storage locations for inventory
*/
const warehouseSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
index: true
},
warehouseCode: {
type: String,
required: true,
unique: true,
uppercase: true
},
warehouseName: {
type: String,
required: true
},
location: {
address: String,
city: String,
state: String,
country: String,
zipCode: String,
coordinates: {
latitude: Number,
longitude: Number
}
},
warehouseType: {
type: String,
enum: ['main', 'regional', 'distribution', 'retail', 'virtual'],
default: 'main'
},
capacity: {
totalSpace: {
type: Number,
default: 0
},
usedSpace: {
type: Number,
default: 0
},
unit: {
type: String,
enum: ['sqft', 'sqm', 'cubic_ft', 'cubic_m'],
default: 'sqft'
}
},
manager: {
name: String,
email: String,
phone: String
},
operatingHours: {
openTime: String,
closeTime: String,
workingDays: [String]
},
status: {
type: String,
enum: ['active', 'inactive', 'maintenance', 'closed'],
default: 'active'
},
isActive: {
type: Boolean,
default: true
}
}, {
timestamps: true
});

// Indexes
warehouseSchema.index({ userId: 1, status: 1 });
warehouseSchema.index({ warehouseCode: 1 });

module.exports = mongoose.model('Warehouse', warehouseSchema);
Loading