<file_sep>const mongoose = require('mongoose');
const _ = require('lodash');
const Model = require('../../models/v2/expenses.model');
const { catchAsync } = require('../errors.controller');
const AppError = require('../../utils/AppError');
const Sale = require('../../models/v2/sales.model');
const Purchase = require('../../models/v2/purchases.model').Model;

module.exports.getAll = catchAsync(async function (req, res, next) {
    const { page, limit, sort, search } = req.query;

    const docs = await Model.paginate(
        { title: { $regex: `${search}`, $options: 'i' } },
        { projection: { __v: 0 }, populate: { path: 'type', select: '-__v' }, lean: true, page, limit, sort }
    );

    res.status(200).json(
        _.pick(docs, ['docs', 'totalDocs', 'hasPrevPage', 'hasNextPage', 'totalPages', 'pagingCounter'])
    );
});

module.exports.addOne = catchAsync(async function (req, res, next) {
    const doc = _.pick(req.body, ['title', 'amount']);

    await Model.create(doc);

    res.status(200).json();
});

module.exports.remove = catchAsync(async function (req, res, next) {
    let ids = req.params.id.split(',');

    for (const id of ids) {
        if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter valid id(s)', 400));
    }

    ids = ids.map((id) => mongoose.Types.ObjectId(id));

    await Model.deleteMany({ _id: { $in: ids } });

    res.status(200).json();
});

module.exports.getKhaata = catchAsync(async function (req, res, next) {
    const { page, limit, sort, search } = req.query;

    const salesPromise = Sale.find(
        { $and: [{ isRemaining: true }, { $and: [{ customer: { $ne: null } }, { customer: { $ne: undefined } }] }] },
        { __v: 0, isRemaining: 0 }
    )
        .populate({ path: 'customer', select: '_id name' })
        .lean();

    const inventoriesPromise = Purchase.find({ isRemaining: true }, { __v: 0, isRemaining: 0 })
        .populate({ path: 'supplier', select: '_id name' })
        .lean();

    const [sales, inventories] = await Promise.all([salesPromise, inventoriesPromise]);

    const data = [
        ...sales.map((s) => ({ ...s, type: 'sale' })),
        ...inventories.map((i) => ({ ...i, type: 'inventory' })),
    ];

    const totalDocs = data.length;
    const offset = page * limit;

    const docs = data.slice(0, limit);
    const totalPages = Math.ceil(totalDocs / limit);
    const hasPrevPage = page > 1;
    const hasNextPage = totalDocs > offset;
    const pagingCounter = (page - 1) * offset + 1;

    res.status(200).json({ docs, totalDocs, totalPages, hasPrevPage, hasNextPage, pagingCounter });
});
<file_sep>const router = require('express').Router();

const autoParams = require('../../utils/autoParams');
const { getAll, addOne, remove, edit } = require('../../controllers/v2/inventories.controller');

router.get('/', autoParams, getAll);
router.route('/').post(addOne);
router.patch('/', edit);

router.route('/id/:id').delete(remove);

module.exports = router;
<file_sep>const mongoose = require('mongoose');
const _ = require('lodash');
const dayjs = require('dayjs');
const Model = require('../../models/v2/inventories.model');
const Product = require('../../models/v2/products.model');
const Unit = require('../../models/v2/units.model');
const { convertUnitsOfInventory, convertUnits } = require('../../models/v2/purchases.model');
const { catchAsync } = require('../errors.controller');
const AppError = require('../../utils/AppError');
const { readQuantityFromString } = require('../../utils/readUnit');
const Logger = require('../../utils/logger');
const stringify = require('../../utils/stringify');

const logger = Logger('inventories');

async function createInventories(inventories, next) {
    const body = inventories.map((b) => _.pick(b, ['product', 'quantity', 'variants']));

    logger.debug(`createInventories() body ${stringify(body)}`);

    const productIds = [...new Set(body.map((b) => b.product))].map((e) => mongoose.Types.ObjectId(e));

    logger.debug(`createInventories() productIds ${stringify(productIds)}`);

    const productsInDB = await Product.find({ _id: { $in: productIds } }, { __v: 0 }).lean();

    logger.debug(`createInventories() productsInDB ${stringify(productsInDB)}`);

    if (productsInDB.length < productIds.length) return next(new AppError('Product(s) does not exist', 404));

    const inventoriesInDB = await Model.find({ 'product._id': { $in: productIds } }).lean();

    logger.debug(`createInventories() inventoriesInDB ${stringify(inventoriesInDB)}`);

    const promises = [];

    body.forEach((b, index) => {
        logger.debug(`createInventories() b${index} ${stringify(b)}`);

        b.product = productsInDB.find((e) => e._id.toString() === b.product.toString());

        logger.debug(`createInventories() b.product ${stringify(b.product)}`);

        const inventoryInDB = inventoriesInDB.find((e) => e.product._id.toString() === b.product._id.toString());

        logger.debug(`createInventories() inventoryInDB ${stringify(inventoryInDB)}`);

        if (inventoryInDB) {
            if (b.quantity) {
                inventoryInDB.quantity +=
                    typeof b.quantity === 'number'
                        ? b.quantity
                        : readQuantityFromString(b.quantity, b.product.unit.value);
                logger.debug(`createInventories() inventoryInDB.quantity ${stringify(inventoryInDB.quantity)}`);
            } else if (b.variants) {
                Object.entries(b.variants).forEach(([key, value], i) => {
                    let q = inventoryInDB.variants[key] ?? 0;
                    logger.debug(`createInventories() key-value-q-${i} ${stringify({ key, value, q })}`);
                    q += typeof value === 'number' ? value : readQuantityFromString(value, b.product.unit.value);
                    inventoryInDB.variants[key] = q;
                });
            } else return next(new AppError('Something went wrong', 400));

            promises.push(Model.findByIdAndUpdate(inventoryInDB._id, inventoryInDB));
        } else if (!inventoryInDB) {
            const newInventory = { product: b.product };
            if (b.quantity) {
                newInventory.quantity =
                    typeof b.quantity === 'number'
                        ? b.quantity
                        : readQuantityFromString(b.quantity, b.product.unit.value);
            } else if (b.variants) {
                const variants = {};
                Object.entries(b.variants).forEach(([key, value]) => {
                    variants[key] =
                        typeof value === 'number' ? value : readQuantityFromString(value, b.product.unit.value);
                });
                newInventory.variants = variants;
            } else return next(new AppError('Something went wrong', 400));

            promises.push(Model.create(newInventory));
        }
    });

    await Promise.all(promises);
}

async function consumeInventories(inventories, next) {
    const body = inventories.map((b) => _.pick(b, ['product', 'quantity', 'variants']));

    logger.debug(`consumeInventories() body ${stringify(body)}`);

    const productIds = [...new Set(body.map((b) => b.product))].map((e) => mongoose.Types.ObjectId(e));

    logger.debug(`consumeInventories() productIds ${stringify(productIds)}`);

    const productsInDB = await Product.find({ _id: { $in: productIds } }, { __v: 0 }).lean();

    logger.debug(`consumeInventories() productsInDB ${stringify(productsInDB)}`);

    if (productsInDB.length < productIds.length) return next(new AppError('Product(s) does not exist', 404));

    const inventoriesInDB = await Model.find({ 'product._id': { $in: productIds } }).lean();

    logger.debug(`consumeInventories() inventoriesInDB ${stringify(inventoriesInDB)}`);

    const promises = [];

    body.forEach((b, index) => {
        logger.debug(`consumeInventories() b${index} ${stringify(b)}`);

        b.product = productsInDB.find((e) => e._id.toString() === b.product.toString());

        logger.debug(`consumeInventories() b.product ${stringify(b.product)}`);

        const inventory = inventoriesInDB.find((e) => e.product._id.toString() === b.product._id.toString());

        logger.debug(`consumeInventories() inventory ${stringify(inventory)}`);

        if (!inventory) return next(new AppError('Product not in stock', 404));

        const { type, unit } = inventory.product;

        if (type.title.toLowerCase() === 'tile') {
            const inventoryVariants = { ...inventory.variants };
            Object.entries(b.variants).forEach(([key, value]) => {
                const quantity = typeof value === 'number' ? value : readQuantityFromString(value, unit.value);
                logger.debug(`consumeInventories() key-value-quantity ${stringify({ key, value, quantity })}`);

                const inventoryQuantity = inventory.variants[key] - quantity;

                logger.debug(
                    `consumeInventories() key-value-inventoryQuantity ${stringify({ key, value, inventoryQuantity })}`
                );

                if (inventoryQuantity < 0) return next(new AppError('Insufficient inventory', 404));

                inventoryVariants[key] = inventoryQuantity;
            });

            Object.entries(inventoryVariants).forEach(([key, value]) => {
                if (value === 0) delete inventoryVariants[key];
            });

            promises.push(Model.findByIdAndUpdate(inventory._id, { variants: inventoryVariants }));
        } else {
            const quantity = typeof b.value === 'number' ? b.value : readQuantityFromString(b.quantity, unit.value);
            inventory.quantity -= quantity;

            if (inventory.quantity === 0) promises.push(Model.findByIdAndDelete(inventory._id));
            else if (inventory.quantity < 0) return next(new AppError('Insufficient inventory', 404));
            else promises.push(Model.findByIdAndUpdate(inventory._id, { quantity: inventory.quantity }));
        }
    });

    await Promise.all(promises);
}

module.exports.createInventories = createInventories;
module.exports.consumeInventories = consumeInventories;

module.exports.getAll = catchAsync(async function (req, res, next) {
    const { page, limit, sort, search } = req.query;

    const results = await Model.paginate(
        {
            'product.modelNumber': { $regex: `${search}`, $options: 'i' },
        },
        { projection: { __v: 0 }, lean: true, page, limit, sort }
    );

    // eslint-disable-next-line no-param-reassign
    results.docs.forEach((d) => {
        const unit = d.product.unit.value;
        if (d.quantity !== undefined && d.quantity !== null) d.quantity = convertUnits(d.quantity, unit);
        else if (d.variants) {
            Object.entries(d.variants).forEach(([key, value]) => {
                d.variants[key] = convertUnits(value, unit);
            });
        }
    });

    res.status(200).json(
        _.pick(results, ['docs', 'totalDocs', 'hasPrevPage', 'hasNextPage', 'totalPages', 'pagingCounter'])
    );
});

module.exports.addOne = catchAsync(async function (req, res, next) {
    const inventories = req.body;
    await createInventories(inventories, next);
    res.status(200).json();
});

module.exports.edit = catchAsync(async function (req, res, next) {
    const inventories = req.body;

    logger.debug(`edit() inventories ${stringify(inventories)}`);

    const deleted = await Model.findOneAndDelete({ 'product._id': mongoose.Types.ObjectId(inventories[0].product) });

    logger.debug(`edit() deleted ${stringify(deleted)}`);

    await createInventories(inventories, next);

    res.status(200).json();
});

module.exports.remove = catchAsync(async function (req, res, next) {
    let ids = req.params.id.split(',');

    for (const id of ids) {
        if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter valid id(s)', 400));
    }

    ids = ids.map((id) => mongoose.Types.ObjectId(id));

    await Model.deleteMany({ _id: { $in: ids } });

    res.status(200).json();
});
<file_sep>const mongoose = require('mongoose');
const mongoosePagiante = require('mongoose-paginate-v2');

const schema = new mongoose.Schema({
    name: {
        type: String,
        required: [true, 'Please enter a name'],
    },
    phone: String,
    // createdBy: { type: mongoose.ObjectId, ref: 'User', select: false },
    createdAt: { type: Date, required: true, default: Date.now() },
});

schema.plugin(mongoosePagiante);

const Model = mongoose.model('Customer', schema);

module.exports = Model;
<file_sep>const { promisify } = require('util');
const _ = require('lodash');
const mongoose = require('mongoose');
const jwt = require('jsonwebtoken');
const { signToken } = require('../utils/jwt');
const User = require('../models/users.model');
const { catchAsync } = require('./errors.controller');
const AppError = require('../utils/AppError');

module.exports.getUsers = catchAsync(async function (req, res, next) {
    const { page, limit, sort, search } = req.query;

    const results = await User.paginate(
        { name: { $regex: `${search}`, $options: 'i' } },
        { projection: { __v: 0, password: 0 }, lean: true, page, limit, sort: { isConfirmed: 1 } }
    );

    res.status(200).json(
        _.pick(results, ['docs', 'totalDocs', 'hasPrevPage', 'hasNextPage', 'totalPages', 'pagingCounter'])
    );
});

module.exports.registerUser = catchAsync(async function (req, res, next) {
    const newUser = _.pick(req.body, ['name', 'password', '<PASSWORD>']);

    if (!Object.keys(newUser).length) return next(new AppError('Please enter a valid user', 400));

    const createdUser = await User.create(newUser);

    const token = signToken(createdUser._id);

    const filteredUser = _.pick(createdUser, ['_id', 'name', 'role']);

    res.status(200).json({ ...filteredUser, token });
});

module.exports.loginUser = catchAsync(async function (req, res, next) {
    const body = _.pick(req.body, ['name', 'password']);

    if (Object.keys(body).length < 2) return next(new AppError('Please enter email and password', 400));

    const user = await User.findOne({ name: body.name });

    if (!user) return next(new AppError('Invalid username or password', 401));

    const isValidPassword = await user.isValidPassword(body.password, user.password);

    if (!isValidPassword) return next(new AppError('Invalid username or password', 401));

    if (!user.isConfirmed) return next(new AppError('Your access is pending', 403));

    const token = signToken(user._id);

    res.status(200).json(token);
});

module.exports.protect = catchAsync(async function (req, res, next) {
    let token;

    if (req.headers.authorization) {
        if (req.headers.authorization === 'dev') return next();

        if (req.headers.authorization.startsWith('Bearer '))
            // eslint-disable-next-line prefer-destructuring
            token = req.headers.authorization.split(' ')[1];
    }

    if (!token) return next(new AppError('Please login to get access', 401));

    const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);

    const freshUser = await User.findById(decoded.id);

    if (!freshUser) return next(new AppError('Please login again', 401));

    const hasChangedPassword = freshUser.changedPasswordAfter(decoded.iat);
    if (hasChangedPassword) return next(new AppError('Please login again', 401));

    const { isConfirmed } = freshUser;

    if (!isConfirmed) return next(new AppError('Your access is pending', 403));

    res.locals.user = freshUser;

    next();
});

module.exports.allowAccess = (...roles) =>
    async function (req, res, next) {
        if (!roles.includes(req.user.role)) return next(new AppError('Unauthorized access to this route', 403));
        next();
    };

module.exports.confirmUser = catchAsync(async function (req, res, next) {
    const { id: userId, role } = req.params;

    if (!mongoose.isValidObjectId(userId)) return next(new AppError('Please enter a valid id', 400));

    const user = await User.findById(userId);

    if (!user) return next(new AppError('User does not exist', 404));

    await user.updateOne({ isConfirmed: true, role: role.toUpperCase() });

    res.status(200).json(_.pick(user, ['_id', 'name', 'role', 'createdAt']));
});

module.exports.editUser = catchAsync(async function (req, res, next) {
    const { id: userId } = req.params;

    if (res.locals.user._id.toString() !== userId)
        return next(new AppError('You are not authorized to edit user', 403));

    const newUser = _.pick(req.body, ['name', 'password', 'passwordConfirm']);

    if (!Object.keys(newUser).length) return next(new AppError('Please enter a valid user', 400));
    if (!mongoose.isValidObjectId(userId)) return next(new AppError('Please enter a valid id', 400));

    const user = await User.findById(userId);

    if (!user) return next(new AppError('User does not exist', 404));
    if (newUser.password) user.password = <PASSWORD>;
    if (newUser.name) user.name = newUser.name;
    if (newUser.passwordConfirm) user.passwordConfirm = <PASSWORD>;
    await user.save();

    res.status(200).json();
});

module.exports.authorizeUser = catchAsync(async function (req, res, next) {
    const { id: userId, role } = req.params;

    if (!mongoose.isValidObjectId(userId)) return next(new AppError('Please enter a valid id', 400));

    const user = await User.findById(userId);

    if (!user) return next(new AppError('User does not exist', 404));

    await user.update({ role: role.toUpperCase() }, { runValidators: true });

    res.status(200).json();
});

module.exports.decodeToken = catchAsync(async function (req, res, next) {
    const { token } = req.params;
    const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
    const user = await User.findById(decoded.id, { __v: 0, password: 0 }).lean();
    res.status(200).json(user);
});

module.exports.remove = catchAsync(async function (req, res, next) {
    let ids = req.params.id.split(',');

    for (const id of ids) {
        if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter valid id(s)', 400));
    }

    ids = ids.map((id) => mongoose.Types.ObjectId(id));

    await User.deleteMany({ _id: { $in: ids } });

    res.status(200).json();
});
<file_sep>const mongoose = require('mongoose');
const _ = require('lodash');
const Model = require('../models/employees.model');
const { catchAsync } = require('./errors.controller');
const AppError = require('../utils/AppError');
const { getSalaries } = require('./v2/expenses.controller');

module.exports.getAll = catchAsync(async function (req, res, next) {
    const { page, limit, sort, search } = req.query;

    const results = await Model.paginate(
        {
            $or: [{ name: { $regex: `${search}`, $options: 'i' } }],
        },
        { projection: { __v: 0 }, populate: 'employee', lean: true, page, limit, sort }
    );

    res.status(200).json(
        _.pick(results, ['docs', 'totalDocs', 'hasPrevPage', 'hasNextPage', 'totalPages', 'pagingCounter'])
    );
});

module.exports.getOne = catchAsync(async function (req, res, next) {
    const { id } = req.params;
    const { page, limit, sort } = req.query;

    if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter a valid id', 400));

    const employee = await Model.findById(id, { __v: 0 }).lean();

    if (!employee) return next(new AppError('Employee does not exist', 404));

    const salaries = await getSalaries({ query: { 'employee._id': id }, page, limit, sort, next });

    res.status(200).json({
        employee,
        salaries: _.omit(salaries, ['page', 'prevPage', 'nextPage', 'limit']),
    });
});

module.exports.addMany = catchAsync(async function (req, res, next) {
    const docs = req.body;

    if (!docs || !docs.length) return next(new AppError('Please enter valid employees', 400));

    await Model.insertMany(docs);

    res.status(200).json();
});

module.exports.addOne = catchAsync(async function (req, res, next) {
    const newDoc = _.pick(req.body, ['name', 'phone', 'cnic', 'address', 'salary']);
    await Model.create(newDoc);
    res.status(200).send();
});

module.exports.edit = catchAsync(async function (req, res, next) {
    const newDoc = _.pick(req.body, ['name', 'phone', 'cnic', 'address', 'salary']);
    const employeeId = req.params.id;
    if (!mongoose.isValidObjectId(employeeId)) return next(new AppError('Invalid employee id', 400));
    const employee = await Model.findById(employeeId);
    if (!employee) return next(new AppError('Employee does not exist'));

    employee.name = newDoc.name;
    employee.phone = newDoc.phone;
    employee.cnic = newDoc.cnic;
    employee.address = newDoc.address;
    employee.salary = newDoc.salary;

    await employee.save();

    res.status(200).send();
});

module.exports.edit = catchAsync(async function (req, res, next) {
    const { id } = req.params;

    if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter a valid id', 400));

    const newDoc = _.pick(req.body, ['name', 'phone', 'cnic', 'address', 'salary']);

    if (!Object.keys(newDoc).length) return next(new AppError('Please enter a valid employee', 400));

    await Model.updateOne({ _id: id }, newDoc, { runValidators: true });

    res.status(200).json();
});

module.exports.remove = catchAsync(async function (req, res, next) {
    let ids = req.params.id.split(',');

    for (const id of ids) {
        if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter valid id(s)', 400));
    }

    ids = ids.map((id) => mongoose.Types.ObjectId(id));

    await Model.deleteMany({ _id: { $in: ids } });

    res.status(200).json();
});
<file_sep>const mongoose = require('mongoose');
const _ = require('lodash');
const Model = require('../../models/v2/salaries.model');

const { catchAsync } = require('../errors.controller');
const AppError = require('../../utils/AppError');
const Employee = require('../../models/employees.model');

module.exports.getAll = catchAsync(async function (req, res, next) {
    const { page, limit } = req.query;
    const docs = await Model.paginate(
        {},
        {
            projection: { __v: 0 },
            page,
            limit,
        }
    );
    res.status(200).json(_.omit(docs, ['nextPage', 'prevPage', 'page', 'limit', 'offset']));
});

module.exports.addOne = catchAsync(async function (req, res, next) {
    const body = _.pick(req.body, ['employee', 'amount']);
    if (!mongoose.isValidObjectId(body.employee)) return next(new AppError('Invalid employee id', 400));
    const employee = await Employee.findById(body.employee);
    if (!employee) return next(new AppError('Employee does not exist', 404));
    body.employee = employee;
    await Model.create(body);
    res.status(200).send();
});

module.exports.edit = catchAsync(async function (req, res, next) {
    const body = _.pick(req.body, ['employee', 'amount']);
    const salaryId = req.params.id;
    if (!mongoose.isValidObjectId(salaryId)) return next(new AppError('Invalid salary id', 400));

    const salary = Model.findById(salaryId);

    if (!salary) return next(new AppError('Salary does not exist', 404));

    if (!mongoose.isValidObjectId(body.employee)) return next(new AppError('Invalid employee id', 400));
    const employee = await Employee.findById(body.employee);
    if (!employee) return next(new AppError('Employee does not exist', 404));
    body.employee = employee;

    salary.employee = body.employee;
    salary.amount = body.amount;

    await salary.save();
    res.status(200).send();
});

module.exports.remove = catchAsync(async function (req, res, next) {
    let ids = req.params.id.split(',');

    for (const id of ids) {
        if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter valid id(s)', 400));
    }

    ids = ids.map((id) => mongoose.Types.ObjectId(id));

    await Model.deleteMany({ _id: { $in: ids } });

    res.status(200).json();
});
<file_sep>const router = require('express').Router();

const {
    getAll,
    addOne,
    remove,
    getCount,
    refund,
    pay,
    getOne,
    edit,
} = require('../../controllers/v2/sales.controller');
const autoParams = require('../../utils/autoParams');

router.get('/count', getCount);
router.get('/id/:id', getOne);

router.get('/', autoParams, getAll);
router.route('/').post(addOne);
router.patch('/id/:id', edit);
router.route('/pay/id/:id/amount/:amount').post(pay);
router.route('/id/:id').delete(remove);

module.exports = router;
<file_sep>const mongoose = require('mongoose');
const mongoosePagiante = require('mongoose-paginate-v2');

const schema = new mongoose.Schema({
    modelNumber: {
        type: String,
        required: [true, 'Please enter a model number'],
    },
    type: {
        type: mongoose.SchemaTypes.Mixed,
        required: [true, 'Type is required'],
    },
    unit: { type: mongoose.SchemaTypes.Mixed, required: [true, 'Unit is required'] },
    variants: {},
    // createdBy: { type: mongoose.ObjectId, ref: 'User', select: false },
    createdAt: { type: Date, required: true, default: Date.now() },
});
schema.plugin(mongoosePagiante);
const Model = mongoose.model('Product', schema);

module.exports = Model;
<file_sep>## Diamond Tiles POS Backend
###### Developed and Maintained by [<NAME>](http://github.com/muneeebnaveeed)
Backend application for serving data from MongoDB database to React frontend (built in NodeJS)
#### Initializing
- Install project dependencies. To install the project dependencies:
`$ npm install`

- Add two environment files named `.development.env` and `.production.env` and add the following block of code to run the project as intended:

```ruby
DB_CONNECTION_STRING=mongodb://127.0.0.1:27017
PORT=5100
JWT_SECRET=secret
JWT_EXPIRES_IN=30d
```

- Run the application in development mode using:
`$ npm run dev`

- Run the application using:
`$ npm start`
<file_sep>const express = require('express');
const path = require('path');
const dotenv = require('dotenv');
const cors = require('cors');

const chalk = require('chalk');
const Database = require('./utils/db');
const AppError = require('./utils/AppError');

const tilesRoute = require('./routes/v2/products.route');
const customersRoute = require('./routes/v2/customers.route');
const employeesRoute = require('./routes/employees.route');
const suppliersRoute = require('./routes/v2/suppliers.route');
const typesRoute = require('./routes/v2/types.route');
const unitsRoute = require('./routes/v2/units.route');
const inventoriesRoute = require('./routes/v2/inventories.route');
const purchasesRoute = require('./routes/v2/purchases.route');

const salesRoute = require('./routes/v2/sales.route');
const expensesRoute = require('./routes/v2/expenses.route');
const salariesRoute = require('./routes/v2/salaries.route');

const dashboardRoute = require('./routes/dashboard.route');

const authRoute = require('./routes/auth.route');
const { errorController } = require('./controllers/errors.controller');
const { protect } = require('./controllers/auth.controller');
const Logger = require('./utils/logger');

const app = express();

dotenv.config({ path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV}`) });

const port = process.env.PORT || 5500;
const logger = Logger('app');

app.listen(port, () => {
    logger.info(`Server running on PORT ${chalk.green(port)}`);

    new Database()
        .connect()
        .then(() => logger.info('Connected to DB'))
        .catch((err) => logger.error('Unable to connect to DB', err));

    app.use(express.json());

    app.use(cors());

    app.get('/', (req, res) => {
        res.status(200).send(`Server running at PORT ${port}`);
    });

    app.use('/products', protect, tilesRoute);
    app.use('/customers', protect, customersRoute);
    app.use('/employees', protect, employeesRoute);
    app.use('/salaries', protect, salariesRoute);

    app.use('/suppliers', protect, suppliersRoute);
    app.use('/types', protect, typesRoute);
    app.use('/units', protect, unitsRoute);
    app.use('/inventories', protect, inventoriesRoute);
    app.use('/purchases', protect, purchasesRoute);

    app.use('/sales', protect, salesRoute);
    app.use('/expenses', protect, expensesRoute);
    app.use('/dashboard', protect, dashboardRoute);

    app.use('/auth', authRoute);

    app.use('*', (req, res, next) => next(new AppError(`Cannot find ${req.originalUrl} on the server!`, 404)));

    app.use(errorController);
});
<file_sep>const mongoose = require('mongoose');
const _ = require('lodash');
const Model = require('../../models/v2/units.model');
const Type = require('../../models/v2/types.model');
const { catchAsync } = require('../errors.controller');
const AppError = require('../../utils/AppError');

module.exports.getAll = catchAsync(async function (req, res, next) {
    const { type } = req.query;

    let query = {};

    if (type) {
        if (!mongoose.isValidObjectId(type)) return next(new AppError('Invalid Type ID', 400));
        query = { 'type._id': type };
    }

    const docs = await Model.find(query, { __v: 0 }).lean();

    res.status(200).json(docs);
});

module.exports.addOne = catchAsync(async function (req, res, next) {
    const newDoc = _.pick(req.body, ['title', 'value', 'type']);

    if (Object.keys(newDoc).length !== 3) return next(new AppError('Please enter a valid unit', 400));

    const type = await Type.findById(newDoc.type, { __v: 0 }).lean();

    if (!type) return next(new AppError('Type does not exist', 404));

    newDoc.type = type;

    await Model.create(newDoc);
    res.status(200).send();
});

module.exports.remove = catchAsync(async function (req, res, next) {
    let ids = req.params.id.split(',');

    for (const id of ids) {
        if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter valid id(s)', 400));
    }

    ids = ids.map((id) => mongoose.Types.ObjectId(id));

    await Model.deleteMany({ _id: { $in: ids } });

    res.status(200).json();
});
<file_sep>const stringify = (json) => JSON.stringify(json, null, 2);

module.exports = stringify;
<file_sep>const mongoose = require('mongoose');
const mongoosePagiante = require('mongoose-paginate-v2');

const schema = new mongoose.Schema({
    name: {
        type: String,
        required: [true, 'Please enter a name'],
    },
    phone: String,
    cnic: String,
    address: String,
    salary: {
        type: String,
        required: [true, 'Please enter salary'],
    },
    // createdBy: { type: mongoose.ObjectId, ref: 'User', select: false },
    createdAt: { type: Date, required: true, default: Date.now() },
});
schema.index({ name: 'text', phone: 'text' });
schema.plugin(mongoosePagiante);
const Model = mongoose.model('Employee', schema);

module.exports = Model;
<file_sep>const router = require('express').Router();

const autoParams = require('../../utils/autoParams');
const { getAll, addOne, remove, getCount, getOne, edit } = require('../../controllers/v2/purchases.controller');

router.get('/count', getCount);
router.get('/', autoParams, getAll);
router.get('/id/:id', getOne);
router.patch('/id/:id', edit);

router.route('/').post(addOne);
router.route('/id/:id').delete(remove);

module.exports = router;
<file_sep>const router = require('express').Router();
const autoParams = require('../utils/autoParams');

const { getAll, addOne, edit, remove, getOne } = require('../controllers/employees.controller');

router.get('/', autoParams, getAll);
router.get('/id/:id', autoParams, getOne);
router.route('/').post(addOne);
router.get('/id/:id', edit);

router.route('/id/:id').patch(edit);
router.route('/id/:id').delete(remove);

module.exports = router;
<file_sep>const mongoose = require('mongoose');
const _ = require('lodash');
const Model = require('../../models/v2/suppliers.model');
const Purchase = require('../../models/v2/purchases.model').Model;

const { catchAsync } = require('../errors.controller');
const AppError = require('../../utils/AppError');
const { convertUnitsOfInventory } = require('../../models/v2/purchases.model');

module.exports.getAll = catchAsync(async function (req, res, next) {
    const { page, limit, sort, search } = req.query;

    const results = await Model.paginate(
        {
            $or: [
                { name: { $regex: `${search}`, $options: 'i' } },
                { phone: { $regex: `${search}`, $options: 'i' } },
                { company: { $regex: `${search}`, $options: 'i' } },
            ],
        },
        { projection: { __v: 0 }, lean: true, page, limit, sort }
    );

    res.status(200).json(
        _.pick(results, ['docs', 'totalDocs', 'hasPrevPage', 'hasNextPage', 'totalPages', 'pagingCounter'])
    );
});

module.exports.getOne = catchAsync(async function (req, res, next) {
    const { id } = req.params;
    const { page, limit, sort } = req.query;

    if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter a valid id', 400));

    const supplier = await Model.findById(id, { __v: 0 }).lean();

    if (!supplier) return next(new AppError('Supplier does not exist', 404));

    // const product = await Product.findById(inventory.product._id, { __v: 0, inventory: 0 }).lean();

    const inventories = await Purchase.paginate(
        { supplier: id },
        {
            projection: { __v: 0, inventory: 0 },
            populate: { path: 'supplier', select: { __v: 0 } },
            lean: true,
            page: page,
            limit: limit,
            sort: sort,
        }
    );

    // eslint-disable-next-line no-param-reassign
    inventories.docs.forEach((d) => (d = convertUnitsOfInventory(d)));

    res.status(200).json({
        supplier,
        inventories: _.omit(inventories, ['page', 'prevPage', 'nextPage', 'limit']),
    });
});

module.exports.addOne = catchAsync(async function (req, res, next) {
    const newDoc = _.pick(req.body, ['name', 'phone', 'company']);
    await Model.create(newDoc);
    res.status(200).send();
});

module.exports.edit = catchAsync(async function (req, res, next) {
    const { id } = req.params;

    if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter a valid id', 400));

    const newDoc = _.pick(req.body, ['name', 'phone', 'company']);

    if (!Object.keys(newDoc).length) return next(new AppError('Please enter a valid supplier', 400));

    await Model.findByIdAndUpdate(id, newDoc, { runValidators: true });

    res.status(200).json();
});

module.exports.remove = catchAsync(async function (req, res, next) {
    let ids = req.params.id.split(',');

    for (const id of ids) {
        if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter valid id(s)', 400));
    }

    ids = ids.map((id) => mongoose.Types.ObjectId(id));

    await Model.deleteMany({ _id: { $in: ids } });

    res.status(200).json();
});
<file_sep>const mongoose = require('mongoose');
const _ = require('lodash');
const Model = require('../../models/v2/customers.model');
const Sale = require('../../models/v2/sales.model');
const { catchAsync } = require('../errors.controller');
const AppError = require('../../utils/AppError');

module.exports.getAll = catchAsync(async function (req, res, next) {
    const { page, limit, sort, search } = req.query;

    const results = await Model.paginate(
        {
            $or: [{ name: { $regex: `${search}`, $options: 'i' } }, { phone: { $regex: `${search}`, $options: 'i' } }],
        },
        { projection: { __v: 0 }, lean: true, page, limit, sort }
    );

    res.status(200).json(
        _.pick(results, ['docs', 'totalDocs', 'hasPrevPage', 'hasNextPage', 'totalPages', 'pagingCounter'])
    );
});

module.exports.getOne = catchAsync(async function (req, res, next) {
    const { id } = req.params;
    const { page, limit, sort } = req.query;

    if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter a valid id', 400));

    const customer = await Model.findById(id, { __v: 0 }).lean();

    if (!customer) return next(new AppError('Customer does not exist', 404));

    const sales = await Sale.paginate(
        { customer: mongoose.Types.ObjectId(id) },
        {
            populate: { path: 'customer', select: '-__v' },
            projection: { __v: 0 },
            lean: true,
            page,
            limit,
            sort,
        }
    );

    res.status(200).json({
        customer,
        sales: _.omit(sales, ['page', 'prevPage', 'nextPage', 'limit']),
    });
});

module.exports.addOne = catchAsync(async function (req, res, next) {
    const newDoc = _.pick(req.body, ['name', 'phone']);
    await Model.create(newDoc);
    res.status(200).send();
});

module.exports.edit = catchAsync(async function (req, res, next) {
    const { id } = req.params;

    if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter a valid id', 400));

    const newDoc = _.pick(req.body, ['name', 'phone']);

    if (!Object.keys(newDoc).length) return next(new AppError('Please enter a valid customer', 400));

    await Model.updateOne({ _id: id }, newDoc, { runValidators: true });

    res.status(200).json();
});

module.exports.remove = catchAsync(async function (req, res, next) {
    let ids = req.params.id.split(',');

    for (const id of ids) {
        if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter valid id(s)', 400));
    }

    ids = ids.map((id) => mongoose.Types.ObjectId(id));

    await Model.deleteMany({ _id: { $in: ids } });

    res.status(200).json();
});
<file_sep>const router = require('express').Router();

const { getAll, addOne, remove } = require('../../controllers/v2/units.controller');

router.route('/').get(getAll);
router.route('/').post(addOne);
router.route('/id/:id').delete(remove);

module.exports = router;
<file_sep>const router = require('express').Router();

const {
    getUsers,
    loginUser,
    registerUser,
    confirmUser,
    editUser,
    protect,
    decodeToken,
    remove,
} = require('../controllers/auth.controller');

router.route('/').get(getUsers);
router.get('/decode/:token', decodeToken);
router.route('/confirm/:id/:role').put(confirmUser);
// router.put('/:id', protect, editUser);
router.route('/register').post(registerUser);
router.route('/login').post(loginUser);
router.route('/id/:id').delete(remove);

module.exports = router;
<file_sep>const mongoose = require('mongoose');
const Logger = require('./logger');

const logger = Logger('app');

module.exports = class Database {
    constructor() {
        logger.debug('Created instance of DB');

        this.connectionString = process.env.DB_CONNECTION_STRING;
        // this.authString = this.getAuthString(process.env.DB_PASSWORD);
        this.authString = process.env.DB_CONNECTION_STRING;
    }

    getAuthString(password) {
        return this.connectionString.replace('<password>', password);
    }

    connect() {
        logger.debug('Connecting to DB...');

        return mongoose.connect(this.authString, {
            useNewUrlParser: true,
            useUnifiedTopology: true,
            useFindAndModify: false,
            useCreateIndex: true,
        });
    }
};
<file_sep>const mongoose = require('mongoose');
const mongoosePagiante = require('mongoose-paginate-v2');

const schema = new mongoose.Schema({
    customer: { type: mongoose.Types.ObjectId, ref: 'Customer' },
    products: { type: mongoose.SchemaTypes.Mixed, required: [true, 'Please enter products'] },
    paid: {
        type: Number,
        required: [true, 'Please enter paid price'],
    },
    isRemaining: Boolean,
    totalRetailPrice: Number,
    // createdBy: { type: mongoose.ObjectId, ref: 'User', select: false },
    createdAt: { type: Date, required: true, default: Date.now() },
});

schema.plugin(mongoosePagiante);

const Model = mongoose.model('Sale', schema);

module.exports = Model;
<file_sep>const mongoose = require('mongoose');
const mongoosePagiante = require('mongoose-paginate-v2');

const schema = new mongoose.Schema({
    title: {
        type: String,
        required: [true, 'Please enter a name'],
    },
    value: {
        type: Number,
        required: [true, 'Please enter a value'],
        min: [1, 'Please enter a valid value'],
    },
    type: {
        type: mongoose.model('Type').schema,
        required: [true, 'Type is required'],
    },
    // createdBy: { type: mongoose.ObjectId, ref: 'User', select: false },
    createdAt: { type: Date, required: true, default: Date.now() },
});
schema.plugin(mongoosePagiante);
const Model = mongoose.model('Unit', schema);

module.exports = Model;
<file_sep>const router = require('express').Router();
const autoParams = require('../utils/autoParams');

const { getPurchases, getSales, getRevenue, getProfit, getExpenses } = require('../controllers/dashboard.controller');

router.get('/purchases', autoParams, getPurchases);
router.get('/sales', autoParams, getSales);
router.get('/revenue', autoParams, getRevenue);
router.get('/expenses', autoParams, getExpenses);
router.get('/profit', autoParams, getProfit);

module.exports = router;
<file_sep>const _ = require('lodash');
const Purchase = require('../models/v2/purchases.model').Model;
const Sale = require('../models/v2/sales.model');
const Expense = require('../models/v2/expenses.model');
const Salary = require('../models/v2/salaries.model');

const { catchAsync } = require('./errors.controller');

module.exports.getPurchases = catchAsync(async function (req, res, next) {
    const { startDate, endDate } = req.query;

    console.log(await Purchase.find(), startDate, endDate);

    const results = await Purchase.find({ createdAt: { $gte: startDate, $lte: endDate } }).lean();
    const prices = results.map((item) => item.totalSourcePrice);
    let sum = 0;
    if (prices.length > 1) sum = prices.reduce((prev, curr) => prev + curr, 0);
    else if (prices.length === 1) sum = prices[0];

    res.status(200).send({
        sum,
        count: results.length,
    });
});

module.exports.getSales = catchAsync(async function (req, res, next) {
    const { startDate, endDate } = req.query;

    const results = await Sale.find({ createdAt: { $gte: startDate, $lte: endDate } }).lean();

    const prices = results.map((item) => item.totalRetailPrice);
    let sum = 0;
    if (prices.length > 1) sum = prices.reduce((prev, curr) => prev + curr, 0);
    else if (prices.length === 1) sum = prices[0];

    res.status(200).send({
        sum,
        count: results.length,
    });
});
async function getRevenue(startDate, endDate) {
    const sales = await Sale.find({ createdAt: { $gte: startDate, $lte: endDate } }).lean();
    const retailPrices = sales.map((item) => item.totalRetailPrice);
    const sourcePrices = [];

    sales.forEach((sale) => sale.products.forEach((product) => sourcePrices.push(product.sourcePrice)));

    let retailPrice = 0;
    if (retailPrices.length) {
        retailPrice = retailPrices.length > 1 ? retailPrices.reduce((prev, curr) => prev + curr, 0) : retailPrices[0];
    }
    let sourcePrice = 0;
    if (sourcePrices.length) {
        sourcePrice = sourcePrices.length > 1 ? sourcePrices.reduce((prev, curr) => prev + curr, 0) : sourcePrices[0];
    }

    return retailPrice - sourcePrice;
}
module.exports.getRevenue = catchAsync(async function (req, res, next) {
    const { startDate, endDate } = req.query;

    const revenue = await getRevenue(startDate, endDate);

    res.status(200).json(revenue);
});

async function getExpenses(startDate, endDate) {
    const [expenses, salaries] = await Promise.all([
        Expense.find({ createdAt: { $gte: startDate, $lte: endDate } }).lean(),
        Salary.find({ createdAt: { $gte: startDate, $lte: endDate } }).lean(),
    ]);
    const totalExpenses = expenses.map((item) => item.amount).reduce((prev, curr) => prev + curr, 0);

    const totalSalaries = salaries.map((item) => item.amount).reduce((prev, curr) => prev + curr, 0);

    return totalExpenses + totalSalaries;
}

module.exports.getProfit = catchAsync(async function (req, res, next) {
    // Revenue - Expenses
    const { startDate, endDate } = req.query;

    const revenue = await getRevenue(startDate, endDate);
    const expenses = await getExpenses(startDate, endDate);

    res.status(200).json({ profit: revenue - expenses });
});

module.exports.getExpenses = catchAsync(async function (req, res, next) {
    const { startDate, endDate } = req.query;

    const amount = await getExpenses(startDate, endDate);
    res.status(200).json(amount);
});
<file_sep>const mongoose = require('mongoose');
const _ = require('lodash');
const Model = require('../../models/v2/types.model');
const Product = require('../../models/v2/products.model');
const Unit = require('../../models/v2/units.model');

const { catchAsync } = require('../errors.controller');
const AppError = require('../../utils/AppError');

module.exports.getAll = catchAsync(async function (req, res, next) {
    const docs = await Model.find({}, { __v: 0 });
    res.status(200).json(docs);
});

module.exports.addOne = catchAsync(async function (req, res, next) {
    const newDoc = _.pick(req.body, ['title']);
    await Model.create(newDoc);
    res.status(200).send();
});

module.exports.remove = catchAsync(async function (req, res, next) {
    let ids = req.params.id.split(',');

    for (const id of ids) {
        if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter valid id(s)', 400));
    }

    ids = ids.map((id) => mongoose.Types.ObjectId(id));

    await Model.deleteMany({ _id: { $in: ids } });

    res.status(200).json();
});
<file_sep>const mongoose = require('mongoose');
const _ = require('lodash');
const Model = require('../../models/v2/products.model');
const Purchase = require('../../models/v2/purchases.model').Model;
const Sale = require('../../models/v2/sales.model');
const Type = require('../../models/v2/types.model');
const { catchAsync } = require('../errors.controller');
const AppError = require('../../utils/AppError');
const Unit = require('../../models/v2/units.model');

module.exports.getAll = catchAsync(async function (req, res, next) {
    const { page, limit, sort, search } = req.query;

    const results = await Model.paginate(
        {
            $or: [{ modelNumber: { $regex: `${search}`, $options: 'i' } }],
        },
        {
            projection: { __v: 0 },
            lean: true,
            page,
            limit,
            sort,
        }
    );

    res.status(200).json(
        _.pick(results, ['docs', 'totalDocs', 'hasPrevPage', 'hasNextPage', 'totalPages', 'pagingCounter'])
    );
});

module.exports.getOne = catchAsync(async function (req, res, next) {
    const { id } = req.params;
    const { inventoriesPage, inventoriesLimit, inventoriesSort, salesPage, salesLimit, salesSort } = req.query;

    if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter a valid id', 400));

    const product = await Model.findById(id, { __v: 0 }).lean();

    if (!product) return next(new AppError('Product does not exist', 404));

    const inventories = await Purchase.paginate(
        { 'product._id': mongoose.Types.ObjectId(id) },
        {
            populate: { path: 'supplier', select: '-__v' },
            projection: { __v: 0 },
            lean: true,
            page: inventoriesPage,
            limit: inventoriesLimit,
            sort: inventoriesSort,
        }
    );

    let relevantInventoryIds = [];
    inventories.docs.forEach((i) => {
        if (i.product && i.product._id) relevantInventoryIds.push(i.product._id.toString());
    });

    relevantInventoryIds = [...new Set(relevantInventoryIds)].map((inventoryId) =>
        mongoose.Types.ObjectId(inventoryId)
    );

    const sales = Sale.paginate(
        { 'inventory._id': { $in: relevantInventoryIds } },
        {
            populate: { path: 'customer', select: '-__v' },
            projection: { __v: 0 },
            lean: true,
            page: salesPage,
            limit: salesLimit,
            sort: salesSort,
        }
    );

    res.status(200).json({
        product,
        inventories: _.omit(inventories, ['page', 'prevPage', 'nextPage', 'limit']),
        sales: _.omit(sales, ['page', 'prevPage', 'nextPage', 'limit']),
    });
});

module.exports.addOne = catchAsync(async function (req, res, next) {
    const newDoc = _.pick(req.body, ['modelNumber', 'type', 'unit']);

    if (Object.keys(newDoc).length < 3) return next(new AppError('Please enter a valid product', 400));

    const [type, unit] = await Promise.all([
        Type.findById(newDoc.type, { __v: 0 }).lean(),
        Unit.findById(newDoc.unit, { __v: 0 }).lean(),
    ]);

    if (!type) return next(new AppError('Type does not exist', 404));
    if (!unit) return next(new AppError('Unit does not exist', 404));

    if (unit.type.title !== type.title) return next(new AppError('Unit not compatible', 400));

    newDoc.type = type;
    newDoc.unit = _.omit(unit, ['type']);

    await Model.create(newDoc);

    res.status(200).send();
});

module.exports.edit = catchAsync(async function (req, res, next) {
    const { id } = req.params;

    if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter a valid id', 400));

    const newDoc = _.pick(req.body, ['modelNumber', 'type', 'retailPrice']);

    if (!Object.keys(newDoc).length) return next(new AppError('Please enter a valid product', 400));

    const [type, unit] = await Promise.all([Type.findById(newDoc.type).lean(), Unit.findById(newDoc.unit)].lean());

    if (!type) return next('Type or unit does not exist', 404);
    if (!unit) return next('Unit or unit does not exist', 404);

    newDoc.type = type;
    newDoc.unit = unit;

    await Model.findByIdAndUpdate(id, newDoc, { runValidators: true });

    res.status(200).json();
});

module.exports.remove = catchAsync(async function (req, res, next) {
    let ids = req.params.id.split(',');

    for (const id of ids) {
        if (!mongoose.isValidObjectId(id)) return next(new AppError('Please enter valid id(s)', 400));
    }

    ids = ids.map((id) => mongoose.Types.ObjectId(id));

    await Model.deleteMany({ _id: { $in: ids } });

    res.status(200).json();
});
