initial commit

This commit is contained in:
Augustine Rapheal 2024-07-13 14:33:19 +01:00
commit ce4ff7eac5
56 changed files with 10708 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

26
.env.example Normal file
View File

@ -0,0 +1,26 @@
MYSQL_DATABASE=
MYSQL_USERNAME=
MYSQL_ROOT_PASSWORD=
MYSQL_HOST=
MYSQL_DOCKER_PORT=
SMTP_HOST=
SMTP_PORT=
SMTP_USER=
SMTP_PASS=
EMAIL_FROM=
ACCESS_TOKEN_PRIVATE_KEY=
REFRESH_TOKEN_PRIVATE_KEY=
ZOHO_CLIENT_ID=
ZOHO_CLIENT_SECRET=
ZOHO_CLIENT_REDIRECT_URI=
ZOHO_BASE_URL=
ZOHO_BOOK_BASE_URL=
ORGANIZATION_ID=
DOLLAR_CURRENCY_ID=
BASE_URL=
BASE_TEST_URL=

105
.gitignore vendored Normal file
View File

@ -0,0 +1,105 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.env.development
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port

1
README.md Normal file
View File

@ -0,0 +1 @@
# financial-forecast

36
config.js Normal file
View File

@ -0,0 +1,36 @@
require('dotenv').config(
process.env.NODE_ENV === 'development' ? { path: `.env.development` } : null
);
module.exports = {
NODE_ENV: process.env.NODE_ENV,
HOST: process.env.HOST,
PORT: process.env.PORT,
MYSQL_DATABASE: process.env.MYSQL_DATABASE,
MYSQL_USERNAME: process.env.MYSQL_USERNAME,
MYSQL_ROOT_PASSWORD: process.env.MYSQL_ROOT_PASSWORD,
MYSQL_HOST: process.env.MYSQL_HOST,
MYSQL_DOCKER_PORT: process.env.DOCKER_PORT,
SMTP_HOST: process.env.SMTP_HOST,
SMTP_PORT: process.env.SMTP_PORT,
SMTP_USER: process.env.SMTP_USER,
SMTP_PASS: process.env.SMTP_PASS,
EMAIL_FROM: process.env.EMAIL_FROM,
ACCESS_TOKEN_PRIVATE_KEY: process.env.ACCESS_TOKEN_PRIVATE_KEY,
REFRESH_TOKEN_PRIVATE_KEY: process.env.REFRESH_TOKEN_PRIVATE_KEY,
ZOHO_CLIENT_ID: process.env.ZOHO_CLIENT_ID,
ZOHO_CLIENT_SECRET: process.env.ZOHO_CLIENT_SECRET,
ZOHO_BASE_URL: process.env.ZOHO_BASE_URL,
ZOHO_BOOK_BASE_URL: process.env.ZOHO_BOOK_BASE_URL,
ZOHO_REFRESH_TOKEN: process.env.ZOHO_REFRESH_TOKEN,
ZOHO_REDIRECT_URI: process.env.ZOHO_REDIRECT_URI,
ORGANIZATION_ID: process.env.ORGANIZATION_ID,
DOLLAR_CURRENCY_ID: process.env.DOLLAR_CURRENCY_ID,
BASE_URL: process.env.BASE_URL,
BASE_TEST_URL: process.env.BASE_TEST_URL,
};

2858
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

44
package.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "manifold-financial-forecast",
"version": "1.0.0",
"description": "an application for predictive cash flow within a given period",
"main": "server.js",
"scripts": {
"start": "NODE_ENV=production node server.js",
"dev": "NODE_ENV=development nodemon server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/manifoldcomputers/financial-forecast.git"
},
"author": "Liassidji Abiodun Manasseh",
"license": "ISC",
"bugs": {
"url": "https://github.com/manifoldcomputers/financial-forecast/issues"
},
"homepage": "https://github.com/manifoldcomputers/financial-forecast#readme",
"dependencies": {
"@fastify/auth": "^3.0.2",
"@fastify/cors": "^8.0.0",
"@fastify/formbody": "^7.0.1",
"axios": "^0.27.2",
"bcrypt": "^5.0.1",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.4",
"dotenv": "^16.0.1",
"dotenv-load": "^2.0.1",
"exceljs": "^4.3.0",
"fastify": "^4.2.0",
"fastify-axios": "^1.2.5",
"handlebars": "^4.7.7",
"jsonwebtoken": "^8.5.1",
"moment": "^2.29.4",
"mysql2": "^2.3.3",
"nodemailer": "^6.7.7",
"nodemon": "^2.0.19",
"path": "^0.12.7",
"sequelize": "^6.21.3",
"uuid": "^8.3.2"
}
}

60
server.js Normal file
View File

@ -0,0 +1,60 @@
//load fastify itself
const app = require('fastify')({
logger: true,
trustProxy: true,
});
const config = require('./config');
const cors = require('@fastify/cors');
const fastifyFormbody = require('@fastify/formbody');
const fastifyAxios = require('fastify-axios');
// load fastify plugins
app
.register(cors, {
origin: '*',
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
})
.register(fastifyFormbody)
.register(fastifyAxios, {
clients: {
zoho: {
baseURL: 'https://www.zohoapis.com/crm/v2/',
// headers: {
// Authorization: `Zoho-oauthtoken ${process.env.ZOHO_TOKEN}`,
// "Content-Type": "application/json",
// },
},
},
});
const db = require('./src/models');
db.sequelize.sync();
app.register(require('./src/routes/auth.routes.js'), { prefix: 'v1' });
app.register(require('./src/routes/zoho.routes.js'), { prefix: 'v1' });
// Declare a route
app.register(
function (instance, options, next) {
instance.setNotFoundHandler(function (request, reply) {
reply.code(404).send({ message: "You're in the wrong place" });
});
next();
},
{ prefix: '/' }
);
// Run the server!
const start = async () => {
try {
const PORT = parseInt(config.PORT);
await app.listen({ port: 4000 });
} catch (err) {
app.log.error(err);
process.exit(1);
}
};
start();
exports.default = app;

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

BIN
src/controllers/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,31 @@
const jwt = require('jsonwebtoken');
const db = require('../../models');
const User = db.users;
const config = require('../../../config');
const generateTokens = async (user) => {
let refreshToken = user.token;
delete user.token;
let payload = {
...user,
};
try {
const accessToken = jwt.sign(payload, config.ACCESS_TOKEN_PRIVATE_KEY, {
expiresIn: '1d',
});
if (refreshToken === null) {
refreshToken = jwt.sign(payload, config.REFRESH_TOKEN_PRIVATE_KEY, {});
const user = await User.findOne({ where: { email: payload.email } });
await user.update({ token: refreshToken });
}
return Promise.resolve({ accessToken, refreshToken });
} catch (e) {
return Promise.reject(e);
}
};
module.exports = { generateTokens };

View File

@ -0,0 +1,37 @@
const jwt = require('jsonwebtoken');
const db = require('../../models');
const config = require('../../../config');
const User = db.users;
const verifyRefreshToken = (refreshToken, reply) => {
const privateKey = config.REFRESH_TOKEN_PRIVATE_KEY;
return new Promise((resolve, reject) => {
const user = User.findOne({
where: { token: refreshToken },
});
if (!user)
return reply.code(500).send({
statusCode: 500,
message: 'Invalid refresh token',
error: true,
});
jwt.verify(refreshToken, privateKey, (err, tokenDetails) => {
if (err)
return reply.code(500).send({
statusCode: 500,
message: 'Invalid refresh token',
error: true,
});
resolve({
tokenDetails,
error: false,
message: 'Valid refresh token',
});
});
});
};
module.exports = { verifyRefreshToken };

View File

@ -0,0 +1,25 @@
const jwt = require('jsonwebtoken');
const db = require('../../models');
const config = require('../../../config');
const User = db.users;
const verifyToken = async (req, reply, done) => {
const token = req?.headers?.authorization?.split(' ')[1];
const decoded = jwt.verify(token, config.ACCESS_TOKEN_PRIVATE_KEY);
if (!decoded) {
throw new Error('Authorization failed');
}
let user = await User.findOne({
where: { email: decoded.email },
});
if (!user) {
throw new Error('Authentication failed');
}
req.user = user.dataValues;
};
module.exports = verifyToken;

BIN
src/controllers/handlers/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,797 @@
let CryptoJS = require('crypto-js');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const { v4: uuidv4 } = require('uuid');
const { sendEmail } = require('../../helpers/email');
const { generateTokens } = require('../auth/generateToken');
const { verifyRefreshToken } = require('../auth/verifyRefreshToken');
const config = require('../../../config');
const db = require('../../models');
const User = db.users;
const Accruedexp = db.accruedexp;
const Prepaidexp = db.prepaidexp;
async function findUserByEmailStatus(email, status, raw = true) {
return await User.findOne({ where: { email, status }, raw: raw });
}
async function findUserByEmail(email) {
return await User.findOne({ where: { email }, raw: true });
}
const savePrepaidPaymentHandler = async (req, reply) => {
const { account_id, tax_id, payment_date, amount, reference_number, customer_id, description, currency_id, currency } = req.body;
let statusCode;
let result;
try {
let prepaidexp;
let paymentDate = new Date(payment_date);
const formattedDate = paymentDate.toISOString().slice(0, 10);
prepaidexp = await Prepaidexp.create({
account_id: account_id,
tax_id: tax_id,
payment_date: formattedDate,
amount: amount,
reference_number: reference_number,
customer_id: customer_id,
description: description,
currency_id: currency_id,
currency: currency,
});
statusCode = 200;
result = {
status: true,
message: 'Prepare expense inserted successfully',
};
} catch (e) {
statusCode = e.code || 500;
result = {
status: false,
message: e.message || 'Internal Server Error',
};
}
return reply.status(statusCode).send(result);
};
const updatePrepaidPaymentHandler = async (req, reply) => {
const { id, paymentConfirmation, account_id, tax_id, payment_date, amount, reference_number, customer_id, description, currency_id, currency } = req.body;
let statusCode;
let result;
try {
// Fetch the existing accrued expense by its ID
let accruedExpense = await Accruedexp.findByPk(id);
if (!accruedExpense) {
statusCode = 404;
result = {
status: false,
message: 'Accrued expense not found',
};
return reply.status(statusCode).send(result);
}
// Update the properties with the provided values
accruedExpense.account_id = account_id;
accruedExpense.tax_id = tax_id;
accruedExpense.payment_date = payment_date;
accruedExpense.amount = amount;
accruedExpense.reference_number = reference_number;
accruedExpense.customer_id = customer_id;
accruedExpense.description = description;
accruedExpense.currency_id = currency_id;
accruedExpense.currency = currency;
accruedExpense.paymentConfirmation = paymentConfirmation;
// Save the updated accrued expense
await accruedExpense.save();
statusCode = 200;
result = {
status: true,
message: 'Accrued expense updated successfully',
};
} catch (e) {
statusCode = e.code || 500;
result = {
status: false,
message: e.message || 'Internal Server Error',
};
}
return reply.status(statusCode).send(result);
};
const updateAccruedPaymentHandler = async (req, reply) => {
const { id, paymentConfirmation, account_id, tax_id, payment_date, amount, reference_number, customer_id, description, currency_id, currency } = req.body;
let statusCode;
let result;
try {
// Fetch the existing accrued expense by its ID
let accruedExpense = await Accruedexp.findByPk(id);
if (!accruedExpense) {
statusCode = 404;
result = {
status: false,
message: 'Accrued expense not found',
};
return reply.status(statusCode).send(result);
}
// Update the properties with the provided values
accruedExpense.account_id = account_id;
accruedExpense.tax_id = tax_id;
accruedExpense.payment_date = payment_date;
accruedExpense.amount = amount;
accruedExpense.reference_number = reference_number;
accruedExpense.customer_id = customer_id;
accruedExpense.description = description;
accruedExpense.currency_id = currency_id;
accruedExpense.currency = currency;
accruedExpense.paymentConfirmation = paymentConfirmation;
// Save the updated accrued expense
await accruedExpense.save();
statusCode = 200;
result = {
status: true,
message: 'Accrued expense updated successfully',
};
} catch (e) {
statusCode = e.code || 500;
result = {
status: false,
message: e.message || 'Internal Server Error',
};
}
return reply.status(statusCode).send(result);
};
const saveAccruedPaymentHandler = async (req, reply) => {
const { account_id, tax_id, payment_date, amount, reference_number, customer_id, description, currency_id, currency } = req.body;
let statusCode;
let result;
try {
let prepaidexp;
let paymentDate = new Date(payment_date);
const dateObject = new Date(paymentDate);
const year = dateObject.getFullYear();
const month = String(dateObject.getMonth() + 1).padStart(2, '0'); // Adding 1 because months are zero-based
const day = String(dateObject.getDate()).padStart(2, '0');
const formattedDate = `${year}-${month}-${day}`;
console.log("O>>>>>>>>>>>>>>>>>>>>>.",formattedDate)
prepaidexp = await Accruedexp.create({
account_id: account_id,
tax_id: tax_id,
payment_date: formattedDate,
amount: amount,
reference_number: reference_number,
customer_id: customer_id,
description: description,
currency_id: currency_id,
currency: currency,
});
statusCode = 200;
console.log(prepaidexp,">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
result = {
status: true,
message: 'Accrued expense inserted successfully',
};
} catch (e) {
statusCode = e.code || 500;
result = {
status: false,
message: e.message || 'Internal Server Error',
};
}
return reply.status(statusCode).send(result);
}
/**
* invite or re-invite a user
* by default user role is 'user'
* @param {string} email - the email of the user.
* @returns {object} - information about the user.
*/
const inviteUserHandler = async (req, reply) => {
const { email } = req.body;
const status = false;
try {
let user;
// check if email doesn't already exist
let userExists = await findUserByEmail(email);
// check if user has already registered
if (userExists && userExists.status)
return reply.code(409).send({
status: false,
message: 'User already exist',
});
// generate a unique token for the user
let token = uuidv4();
let ciphertext = CryptoJS.AES.encrypt(token, 'ManifoldSecret').toString();
// remove special characters to the token
let updatedCipherText = ciphertext
.toString()
.replaceAll('+', 'xMl3Jk')
.replaceAll('/', 'Por21Ld')
.replaceAll('=', 'Ml32');
// invite a new user
if (!userExists) {
// store the data in the database
user = await User.create({
email,
inviteToken: token,
inviteDate: new Date(),
});
}
// re-invite a user
if (userExists && !userExists.status) {
// update the user's status to false
user = await User.update(
{
inviteToken: token,
inviteDate: new Date(),
},
{ where: { email } }
);
}
const details = {
name: 'User',
templateToUse: 'invite',
url: `${config.BASE_URL}/register/${updatedCipherText}`,
};
console.log(details)
// invite user by sending an email
await sendEmail(
email,
'Manifold Forecast Invitation',
'Please click the link below to complete registration',
details
);
console.log(sendEmail)
statusCode = 200;
result = {
status: true,
message: 'Users invited successfully',
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
/**
* login a user
* @param {string} email - the email of the user.
* @param {string} password - the password of the user.
* @returns {object} - information about the user.
*/
const loginUserHandler = async (req, reply) => {
const { email, password } = req.body;
const status = true;
try {
// check if email exists
let user = await findUserByEmailStatus(email, status);
if (!user)
return reply.code(500).send({
status: false,
message: 'Invalid email or password',
});
// check if password matches
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch)
return reply.code(401).send({
status: false,
message: 'Invalid email or password',
});
// generate tokens
const { accessToken, refreshToken } = await generateTokens(user);
statusCode = 200;
result = {
status: true,
message: 'User logged in successfully',
data: {
accessToken,
refreshToken,
isZohoAuthenticated: user.isZohoAuthenticated ? true : false,
},
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
/**
* register a user
* @param {string} firstName - the first name of the user.
* @param {string} lastName - the last name of the user.
* @param {string} email - the email of the user.
* @param {string} password - the password of the user.
* @param {object} inviteToken - the token generated when an invite is sent to a user.
* @returns {object} - information about the user.
*/
const registerUserHandler = async (req, reply) => {
const { firstName, lastName, email, password, inviteToken } = req.body;
try {
// TODO:: check if link has expired
// add special characters to the token
let updatedToken = inviteToken
.toString()
.replaceAll('xMl3Jk', '+')
.replaceAll('Por21Ld', '/')
.replaceAll('Ml32', '=');
// decrypt the invitation token
let bytes = CryptoJS.AES.decrypt(updatedToken, 'ManifoldSecret');
let originalText = bytes.toString(CryptoJS.enc.Utf8);
// Hash the password
const hashedPassword = await bcrypt.hash(password, 10);
// check if email and token match the database
let user = await User.findOne({
where: { email, inviteToken: originalText },
});
// check if user has been invited
if (!user)
return reply.code(401).send({
status: false,
message: 'Invalid email or token',
});
// check if user has already completed registration
if (user.status)
return reply.code(409).send({
status: false,
message: 'Already registered',
});
// update the user's status to true
await User.update(
{
firstName,
lastName,
status: true,
password: hashedPassword,
},
{ where: { email } }
);
// send an email to the user to notify them of their registration
const details = {
name: `${firstName} ${lastName}`,
templateToUse: 'signup',
url: `${config.BASE_URL}`,
};
sendEmail(
email,
'Manifold Forecast Signup',
'Registration completed',
details
);
statusCode = 201;
result = {
status: true,
message: 'User registered successfully',
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
// A user who wants to refresh token
const refreshTokenHandler = async (req, reply) => {
try {
const { refreshToken } = req.body;
const { tokenDetails } = await verifyRefreshToken(refreshToken, reply);
if (!tokenDetails) {
return reply.code(401).send({
status: false,
message: 'Invalid email or password',
});
}
delete tokenDetails.exp;
delete tokenDetails.iat;
const accessToken = jwt.sign(
tokenDetails,
config.ACCESS_TOKEN_PRIVATE_KEY,
{ expiresIn: '1d' }
);
statusCode = 200;
// return all information about the user
result = {
status: true,
message: 'Access token created successfully',
data: {
accessToken,
},
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
const getUserHandler = async (req, reply) => {
try {
let user = await User.findOne({
where: { id: req.user.id },
});
if (!user)
return reply.code(404).send({
status: false,
message: 'User Not Found',
});
statusCode = 200;
result = {
status: true,
message: 'User fetched successfully',
data: user,
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
/**
* view all users
* @returns {object} - information about the user.
*/
const getUsersHandler = async (req, reply) => {
try {
let users = await User.findAll();
statusCode = 200;
result = {
status: true,
message: 'Users retrieved successfully',
data: users,
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
/**
* update user role
* @returns {object} - information about the user.
*/
const updateUserRoleHandler = async (req, reply) => {
try {
let { role, email } = req.body;
let isAdmin = req.user.role;
if (!isAdmin)
return reply.code(401).send({
status: false,
message: 'You are not authorized to perform this action',
});
let user = await User.findOne({
where: { email },
});
if (user.id === req.user.id) {
return reply.code(400).send({
status: false,
message: 'You are not allowed to perform this action',
});
}
if (user.super) {
return reply.code(400).send({
status: false,
message: 'You are not allowed to perform this action',
});
}
if (!user.status)
return reply.code(400).send({
status: false,
message: 'User has not completed registration',
});
if (!user)
return reply.code(404).send({
status: false,
message: 'User Not Found',
});
await user.update({ role });
statusCode = 200;
result = {
status: true,
message: 'User role updated successfully',
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
const updatePasswordHandler = async (req, reply) => {
try {
const { currentPassword, newPassword } = req.body;
const { id } = req.user;
const user = await User.findOne({ where: { id } });
if (!user)
return reply.code(404).send({
status: false,
message: 'User not found',
});
// check if the current password is correct
const isMatch = await bcrypt.compare(currentPassword, user.password);
if (!isMatch)
return reply.code(400).send({
status: false,
message: 'Your current password does not match the old password',
});
// hash the new password
const encryptedPassword = await bcrypt.hash(newPassword, 10);
// update the password
await user.update({ password: encryptedPassword });
statusCode = 200;
result = {
status: true,
message: 'Password updated successfully',
data: null,
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
const forgotPasswordHandler = async (req, reply) => {
try {
const { email } = req.body;
// check if email exist and status is false
let userExists = await findUserByEmailStatus(email, true);
if (!userExists)
return reply.code(409).send({
status: false,
message: 'User Not Found',
});
// encrypt the email
let ciphertext = CryptoJS.AES.encrypt(
userExists.email,
'ManifoldSecret'
).toString();
// remove special characters to the token
let updatedCipherText = ciphertext
.toString()
.replaceAll('+', 'xMl3Jk')
.replaceAll('/', 'Por21Ld')
.replaceAll('=', 'Ml32');
const details = {
name: userExists.firstName,
templateToUse: 'passwordReset',
url: `${config.BASE_URL}/reset-password/${updatedCipherText}`,
};
// invite user by sending an email
await sendEmail(
email,
'Reset Manifold Forecast Password',
'Please click the link below to reset password',
details
);
statusCode = 200;
result = {
status: true,
message: 'Password reset link sent successfully',
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
const resetPasswordHandler = async (req, reply) => {
try {
const { password, token } = req.body;
// add special characters to the token
let updatedToken = token
.toString()
.replaceAll('xMl3Jk', '+')
.replaceAll('Por21Ld', '/')
.replaceAll('Ml32', '=');
// decrypt the invitation token
let bytes = CryptoJS.AES.decrypt(updatedToken, 'ManifoldSecret');
let email = bytes.toString(CryptoJS.enc.Utf8);
// check if email exist and status is false
let userExists = await findUserByEmailStatus(email, true, false);
if (!userExists)
return reply.code(409).send({
status: false,
message: 'User Not Found',
});
// hash the new password
const encryptedPassword = await bcrypt.hash(password, 10);
// update the password
await userExists.update({ password: encryptedPassword });
statusCode = 200;
result = {
status: true,
message: 'Password reset successfully',
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
const deleteUserHandler = async (req, reply) => {
try {
// TODO:: only admin should be able to delete a user
const id = req.params.id;
let user = await User.findOne({
where: { id },
});
if (!user)
return reply.code(404).send({
status: false,
message: 'User Not Found',
});
if (user.id === req.user.id) {
return reply.code(400).send({
status: false,
message: 'You are not allowed to perform this action',
});
}
if (user.super) {
return reply.code(400).send({
status: false,
message: 'You are not allowed to perform this action',
});
}
user.destroy();
statusCode = 200;
result = {
status: true,
message: 'User deleted successfully',
data: {},
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
module.exports = {
inviteUserHandler,
loginUserHandler,
registerUserHandler,
getUserHandler,
refreshTokenHandler,
updatePasswordHandler,
forgotPasswordHandler,
resetPasswordHandler,
getUsersHandler,
updateUserRoleHandler,
deleteUserHandler,
savePrepaidPaymentHandler,
saveAccruedPaymentHandler,
updateAccruedPaymentHandler,
updatePrepaidPaymentHandler,
};

View File

@ -0,0 +1,615 @@
const { default: axios } = require("axios");
const ExcelJS = require("exceljs");
const db = require("../../models");
const config = require("../../../config");
const { Op } = require("sequelize");
const Rate = db.rates;
const BillForecast = db.billForecasts;
const InvoiceForecast = db.invoiceForecasts;
const SaleForecast = db.saleForecasts;
const PurchaseForecast = db.purchaseForecasts;
const Invoice = db.invoices;
const ZohoRate = db.zohorates;
const Bill = db.bills;
const InitialBalance = db.initialBalances;
const Sale = db.sales;
const Purchase = db.purchases;
const CustomerPayment = db.customerPayments;
const VendorPayment = db.vendorPayments;
//get inventory
const getZohoInventoryHandler = async(req,reply) =>{
try{
const zohoAccessToken = req.body.accessToken;
if(zohoAccessToken === null || zohoAccessToken===""){
return reply.code(400).send({
status: false,
message: "missing access token",
});
}
const options = {
headers: {
"Content-Type": ["application/json"],
Authorization: "Bearer " + zohoAccessToken,
},
};
console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>""""""""""""""""""""');
var url =`${config.ZOHO_BOOK_BASE_URL}/items?organization_id=${config.ORGANIZATION_ID}`;
//https://books.zoho.com/api/v3/items?organization_id={{Organization.Organization_ID}}
console.log(url);
res = await axios.get(url, options);
if(res.data.error)
{
return reply.code(400).send('error occurred');
}
else if(res.data.code == 0 || res.data.message == 'success'){
// Filter items with available_stock greater than 0
const filteredItems = res.data.items.filter(item => item.available_stock > 0);
// Map filtered items to include only item_id, name, and available_stock
console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
const transformedItems = filteredItems.map(item => ({
item_id: item.item_id,
name: item.name,
available_stock: item.available_stock,
account_name:item.account_name,
description:item.purchase_description,
rate:item.rate,
totalRate: item.rate*item.available_stock,
manufacturer:item.manufacturer
}));
var totalsumofItems = transformedItems.reduce((acc,currval)=>acc +currval.totalRate,0)
var resultTobereturn = {
status: true,
TotalsumofItems:totalsumofItems,
message: "Items fetched successfully",
data: {Items:transformedItems,totalsumofItems:totalsumofItems}
}
console.clear();
console.log(resultTobereturn)
return reply.code(200).send(resultTobereturn);
}
else {
return reply.code(400).send({
status: false,
message: "Failed to fetch items",
});
}
}
catch(e){
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
}
//get the non billable expenses
/*const getNonbillableExpense = async(req,reply)=>
{
console.clear()
console.log("i am insite the word")
try
{
const zohoAccessToken = req.body.accessToken;
console.log(zohoAccessToken,"rttgghxvj zbxz")
if(zohoAccessToken == null || zohoAccessToken=="")
{
return reply.code(400).send({
status: false,
message: "missing access token",
});
}
const options = {
headers: {
"Content-Type": ["application/json"],
Authorization: "Bearer " + zohoAccessToken,
},
};
var url = `${config.ZOHO_BOOK_BASE_URL}/expenses?organization_id=${config.ORGANIZATION_ID}`;
console.log(url,"this is the url")
var res = await axios.get(url, options);
console.log(res,"this is the response");
console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
if(res.data.error){
return reply.code(400).send('error occurred');
}
else if(res.data.code == 0 || res.data.message == 'success'){
const filteredItems = res.data.expenses.filter(item => item.is_billable == false);
const transformedItems = filteredItems.map(item => ({
expense_id: item.expense_id,
date: item.date,
account_name: item.account_name,
description: item.description,
paid_through_account_name: item.paid_through_account_name,
currency_code: item.currency_code,
currency_id: item.currency_id,
total_without_tax: item.total_without_tax,
status: item.status,
}));
return reply.code(200).send({
status: true,
message: "Items fetched successfully",
data: transformedItems,
});
}
else {
return reply.code(400).send({
status: false,
message: "Failed to fetch items",
});
}
}
catch(e){
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
}*/
const getNonbillableExpense = async (req, reply) => {
try {
console.clear();
const hzohoAccessToken = req.body.accessToken;
let getZohoTokenurl = `${config.ZOHO_BASE_URL}?refresh_token=${config.ZOHO_REFRESH_TOKEN}&client_id=${config.ZOHO_CLIENT_ID}&client_secret=${config.ZOHO_CLIENT_SECRET}&redirect_uri=${config.BASE_URL}&grant_type=refresh_token`;
console.log(getZohoTokenurl, "access token"); // Corrected variable name
try {
let zohoRes = await axios.post(getZohoTokenurl);
// Check if access token exists in the response data
var zohoToken = zohoRes.data.access_token;//zohoRes.data.access_token;
console.log(zohoToken)
if (!zohoToken || zohoToken == null) {
return reply.code(400).send({
status: false,
message: "Missing access token",
});
}
} catch (error) {
console.error("Error fetching Zoho token:");
return reply.code(500).send({
status: false,
message: "Failed to fetch Zoho token",
});
}
const options = {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${zohoToken}`,
},
};
const url = `${config.ZOHO_BOOK_BASE_URL}/expenses?organization_id=${config.ORGANIZATION_ID}`;
const response = await axios.get(url, options);
if (response.data.error) {
return reply.code(400).send('Error occurred');
} else if (response.data.code === 0 || response.data.message === 'success') {
const filteredItems = response.data.expenses.filter(item => !item.is_billable);
const transformedItems = filteredItems.map(item => ({
expense_id: item.expense_id,
date: item.date,
account_name: item.account_name,
description: item.description,
paid_through_account_name: item.paid_through_account_name,
currency_code: item.currency_code,
currency_id: item.currency_id,
total_without_tax: item.total_without_tax,
status: item.status,
}));
return reply.code(200).send({
status: true,
message: "Items fetched successfully",
data: transformedItems,
});
} else {
return reply.code(400).send({
status: false,
message: "Failed to fetch items",
});
}
} catch (error) {
console.error('Error:', error);
return reply.code(500).send({
status: false,
message: "Internal Server Error",
});
}
};
const loginUserHandler = async (req, reply) => {
const { email, password } = req.body;
const status = true;
try {
// check if email exists
let user = await findUserByEmailStatus(email, status);
if (!user)
return reply.code(500).send({
status: false,
message: 'Invalid email or password',
});
// check if password matches
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch)
return reply.code(401).send({
status: false,
message: 'Invalid email or password',
});
// generate tokens
const { accessToken, refreshToken } = await generateTokens(user);
statusCode = 200;
result = {
status: true,
message: 'User logged in successfully',
data: {
accessToken,
refreshToken,
isZohoAuthenticated: user.isZohoAuthenticated ? true : false,
},
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
// Get exchange rate and save into database
const getZohoExchangeRateHandler = async (
zohoAccessToken,
forecastNumber,
forecastPeriod,
userId
) => {
const TODAY_START = new Date().setHours(0, 0, 0, 0);
const TODAY_END = new Date().setHours(23, 59, 59, 999);
let rate;
const options = {
headers: {
"Content-Type": ["application/json"],
Authorization: "Bearer " + zohoAccessToken,
},
};
// check if exchange rate exist in db for the day
rate = await Rate.findOne({
where: {
userId,
forecastType: `${forecastNumber} ${forecastPeriod}`,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
// if rate doesn't exist then create the exchange rate from zoho and save to db
if (!rate) {
url = `${config.ZOHO_BOOK_BASE_URL}/settings/currencies/${config.DOLLAR_CURRENCY_ID}/exchangerates?organization_id=${config.ORGANIZATION_ID}`;
res = await axios.get(url, options);
if (res.data.error)
return reply.code(400).send({
status: false,
message: "Could not fetch exchange rate",
});
// save to db
rate = await Rate.create({
userId,
old: res.data.exchange_rates[0].rate,
latest: res.data.exchange_rates[0].rate,
forecastType: `${forecastNumber} ${forecastPeriod}`,
});
}
return rate;
};
const getExchangeRateHandler = async (req, reply) => {
try {
const { number, period } = req.params;
const TODAY_START = new Date().setHours(0, 0, 0, 0);
const TODAY_END = new Date().setHours(23, 59, 59, 999);
const userId = req.user.id;
let rate = await Rate.findOne({
where: {
userId,
forecastType: number + " " + period,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
if (!rate) {
return reply.code(400).send({
status: false,
message: "Could not fetch exchange rate",
});
}
statusCode = 200;
result = {
status: true,
message: "Exchange rate fetched successfully",
data: rate,
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
const getAllExchangeRateHandler = async (req, reply) => {
try {
const zohorates = await ZohoRate.findAll({
limit: 30,
order: [["createdAt", "DESC"]],
});
statusCode = 200;
result = {
status: true,
message: "Exchage rates fetched successfully",
data: zohorates,
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
const downloadExchangeRate = async (req, reply) => {
try {
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet("Exchange Rate List");
const exchangeRateColumns = [
{ key: "id", header: "id" },
{ key: "rate", header: "rate" },
{ key: "createdAt", header: "date" },
];
worksheet.columns = exchangeRateColumns;
const zohorates = await ZohoRate.findAll({
order: [["createdAt", "DESC"]],
});
zohorates.forEach((rate) => {
worksheet.addRow(rate);
});
result = await workbook.xlsx.writeBuffer();
statusCode = 200;
} catch (e) {
statusCode = e.response.status;
result = {
status: false,
message: e.response.data.message,
};
}
return reply.status(statusCode).send(result);
};
const updateExchangeRateHandler = async (req, reply) => {
try {
const TODAY_START = new Date().setHours(0, 0, 0, 0);
const TODAY_END = new Date().setHours(23, 59, 59, 999);
const { id } = req.params;
const { latest, forecastNumber, forecastPeriod } = req.body;
const userId = req.user.id;
// check if exchange rate exist in db for the day
let rate = await Rate.findOne({
where: {
id,
userId,
},
});
// if rate doesn't exist then create the exchange rate from zoho and save to db
if (!rate) {
return reply.code(400).send({
status: false,
message: "Could not fetch exchange rate",
});
}
rate = await rate.update({
userId: userId,
latest,
});
await InitialBalance.destroy({
where: {
userId: userId,
forecastType: `${forecastNumber} ${forecastPeriod}`,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await BillForecast.destroy({
where: {
userId: userId,
forecastType: `${forecastNumber} ${forecastPeriod}`,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await InvoiceForecast.destroy({
where: {
userId: userId,
forecastType: `${forecastNumber} ${forecastPeriod}`,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await SaleForecast.destroy({
where: {
userId: userId,
forecastType: `${forecastNumber} ${forecastPeriod}`,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await PurchaseForecast.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await Bill.destroy({
where: {
userId: userId,
forecastType: `${forecastNumber} ${forecastPeriod}`,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await Invoice.destroy({
where: {
userId: userId,
forecastType: `${forecastNumber} ${forecastPeriod}`,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await Sale.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await Purchase.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await CustomerPayment.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await VendorPayment.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
statusCode = 200;
result = {
status: true,
message: "Exchange rate updated successfully",
data: rate,
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
module.exports = {
getExchangeRateHandler,
getAllExchangeRateHandler,
getZohoExchangeRateHandler,
updateExchangeRateHandler,
downloadExchangeRate,
getNonbillableExpense,
getZohoInventoryHandler
};

View File

@ -0,0 +1,999 @@
const { default: axios } = require("axios");
const ExcelJS = require("exceljs");
const db = require("../../models");
const { Op } = require("sequelize");
let moment = require("moment");
const config = require("../../../config");
const {
createZohoRate,
createOpeningBalance,
createBankAccounts,
getTodayDayOpeningBalance,
fetchAllInvoiceForecast,
fetchAllBillForecast,
fetchAllPurchaseForecast,
fetchAllSaleForecast,
} = require("../../helpers/dbQuery");
const InvoiceForecast = db.invoiceForecasts;
const SaleForecast = db.saleForecasts;
const PurchaseForecast = db.purchaseForecasts;
const BillForecast = db.billForecasts;
const Invoice = db.invoices;
const Sale = db.sales;
const AccruedExpDb = db.accruedexp;
const getPrepaidExpDB = db.prepaidexp;
const Purchase = db.purchases;
const CustomerPayment = db.customerPayments;
const VendorPayment = db.vendorPayments;
const Bill = db.bills;
const InitialBalance = db.initialBalances;
const BankAccount = db.bankAccounts;
const Rate = db.rates;
const Overdraft = db.overdrafts;
const OpeningBalance = db.openingBalances;
const resyncHandler = async (req, reply) => {
const TODAY_START = moment().startOf("day").format();
const TODAY_END = moment().endOf("day").format();
try {
const userId = req.user.id;
await InitialBalance.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await InvoiceForecast.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await SaleForecast.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await PurchaseForecast.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await BillForecast.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await Invoice.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await Sale.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await Purchase.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await Bill.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await CustomerPayment.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await VendorPayment.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
await Rate.destroy({
where: {
userId: userId,
updatedAt: {
[Op.gt]: TODAY_START,
[Op.lt]: TODAY_END,
},
},
});
statusCode = 204;
result = {
status: true,
message: "Resynced successfully",
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
const ACcruedExpHandler = async (req, reply) => {
let statusCode = 200;
let result;
try {
const today = new Date();
today.setHours(0, 0, 0, 0); // Set hours, minutes, seconds, and milliseconds to 0 for comparison with dates only
const getAllAccrued = await AccruedExpDb.findAll();
const filteredExpenses = getAllAccrued.filter(expense => {
const paymentDate = new Date(expense.payment_date);
return expense.paymentConfirmation === 0 && paymentDate < today;
});
result = {
status: true,
message: "Accrued fetched successfully",
data: filteredExpenses,
};
} catch (e) {
statusCode = e.code || 500; // Set status code to 500 if e.code is undefined
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
const PrepaidExpHandler = async (req, reply) => {
let statusCode = 200;
console.log('this place',' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>');
try{
const today = new Date();
let getAllprepaid = await getPrepaidExpDB.findAll();
console.log(getAllprepaid,' >>>>>>>>>>>>>>>>>>>>hhhhhhhhhhhhhhhhh>');
const filteredExpenses = getAllprepaid.filter(expense => {
const paymentDate = new Date(expense.payment_date);
return expense.paymentConfirmation === 0 && paymentDate < today;
});
var result = {
status: true,
message: "Prepaid Expenses fetched successfully",
data: filteredExpenses,
};
}
catch (e) {
statusCode = 500;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
const bankAccountsHandler = async (req, reply) => {
const YESTERDAY_START = moment().subtract(1, "days").startOf("day").format();
const YESTERDAY_END = moment().subtract(1, "days").endOf("day").format();
try {
let bankAccounts = await BankAccount.findAll({
where: {
createdAt: {
[Op.gt]: YESTERDAY_START,
[Op.lt]: YESTERDAY_END,
},
},
});
statusCode = 200;
result = {
status: true,
message: "Opening bank accounts fetched successfully",
data: bankAccounts,
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
const downloadOpeningBalance = async (req, reply) => {
try {
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet("Opening Balance");
const YESTERDAY_START = moment()
.subtract(1, "days")
.startOf("day")
.format();
const YESTERDAY_END = moment().subtract(1, "days").endOf("day").format();
const openingBlanceColumns = [
{ key: "id", header: "id" },
{ key: "accountName", header: "account name" },
{ key: "accoutType", header: "account type" },
{ key: "currency", header: "currency" },
{ key: "balance", header: "balance" },
];
worksheet.columns = openingBlanceColumns;
let bankAccounts = await BankAccount.findAll({
where: {
createdAt: {
[Op.gt]: YESTERDAY_START,
[Op.lt]: YESTERDAY_END,
},
},
});
bankAccounts.forEach((rate) => {
worksheet.addRow(rate);
});
result = await workbook.xlsx.writeBuffer();
statusCode = 200;
} catch (e) {
statusCode = e.response.status;
result = {
status: false,
message: e.response.data.message,
};
}
return reply.status(statusCode).send(result);
};
// This function will be executed by a CRON JOB daily
// stores todays exchange rate and opening in the database
const createOpeningBalanceHandler = async (req, reply) => {
const TODAY_START = moment().startOf("day").format();
const TODAY_END = moment().endOf("day").format();
try {
let todayOpeningBalData = {
today_start: TODAY_START,
today_end: TODAY_END,
};
// check if opening balance has been updated today.
const openingBalance = await getTodayDayOpeningBalance({
todayOpeningBalData,
});
if (openingBalance) {
return reply.code(400).send({
status: false,
message: "Opening Balance Already Exits for today",
});
}
// get zoho access token
url = `${config.ZOHO_BASE_URL}?refresh_token=${config.ZOHO_REFRESH_TOKEN}&client_id=${config.ZOHO_CLIENT_ID}&client_secret=${config.ZOHO_CLIENT_SECRET}&redirect_uri=${config.ZOHO_REDIRECT_URI}&grant_type=refresh_token`;
zoho = await axios.post(url);
if (zoho.data.error) {
return reply.code(400).send({
status: false,
message: "Invalid code",
});
}
const options = {
headers: {
"Content-Type": ["application/json"],
Authorization: "Bearer " + zoho.data.access_token,
},
};
// get current exchange rate from zoho
let rateUrl = `${config.ZOHO_BOOK_BASE_URL}/settings/currencies/${config.DOLLAR_CURRENCY_ID}/exchangerates?organization_id=${config.ORGANIZATION_ID}`;
res = await axios.get(rateUrl, options);
if (res.data.error)
return reply.code(400).send({
status: false,
message: "Could not fetch exchange rate",
});
payload = {
rate: res.data.exchange_rates[0].rate,
};
await createZohoRate({ payload });
// fetch all bank accounts details
let bankAccountUrl = `${config.ZOHO_BOOK_BASE_URL}/bankaccounts?organization_id=${config.ORGANIZATION_ID}`;
resp = await axios.get(bankAccountUrl, options);
if (resp.data.error)
return reply.code(400).send({
status: false,
message: "Could not fetch bank accounts",
});
let ngnBalance = 0;
let usdBalance = 0;
let overdraft;
for (const [rowNum, inputData] of resp.data.bankaccounts.entries()) {
// Save into databse
if (
inputData.currency_code === "USD" ||
inputData.currency_code === "NGN"
) {
overdraft = inputData.balance;
// if bank balance is less than zero then check if there is an overdraft account loan
if (inputData.balance < 0) {
overdraftExits = await Overdraft.findOne({
where: { accountId: inputData.account_id },
});
if (overdraftExits) {
overdraft =
parseFloat(inputData.balance) +
parseFloat(overdraftExits.dataValues.amount);
}
}
let bankAccounts = {
accountId: inputData.account_id,
accountName: inputData.account_name,
accountType: inputData.account_type,
accountNumber: inputData.account_number,
bankName: inputData.bank_name,
currency: inputData.currency_code,
balance: inputData.balance,
overdraftBalance: overdraft,
};
if (inputData.currency_code === "USD") {
usdBalance += overdraft;
}
if (inputData.currency_code === "NGN") {
ngnBalance += overdraft;
}
await createBankAccounts({ bankAccounts });
}
}
payload = {
naira: ngnBalance,
dollar: usdBalance,
};
await createOpeningBalance({ payload });
statusCode = 200;
result = {
status: true,
message: "Successfully",
data: "",
};
} catch (e) {
result = {
status: false,
message: e.response.data.message,
};
}
return reply.status(statusCode).send(result);
};
const createOverdraftHandler = async (req, reply) => {
const YESTERDAY_START = moment().subtract(1, "days").startOf("day").format();
const YESTERDAY_END = moment().subtract(1, "days").endOf("day").format();
const {
accountId,
accountName,
accountType,
accountNumber,
bankName,
currency,
amount,
} = req.body;
try {
let overdraft;
let bankAccount;
let openingBalance;
let bankBalance;
let overdraftExists = await Overdraft.findOne({
where: { accountId },
});
if (overdraftExists)
return reply.code(409).send({
status: false,
message: "Overdraft account already exist",
});
bankAccount = await BankAccount.findOne({
where: {
accountId,
currency,
createdAt: {
[Op.gt]: YESTERDAY_START,
[Op.lt]: YESTERDAY_END,
},
},
});
if (!bankAccount)
return reply.code(404).send({
status: false,
message: "Bank account not found",
});
overdraft = await Overdraft.create({
accountId,
accountName,
accountType,
accountNumber,
bankName,
currency,
amount,
});
if (!overdraft)
return reply.code(500).send({
status: false,
message: "Unable to create overdraft account",
});
if (bankAccount.currency !== currency)
return reply.code(400).send({
status: false,
message: "Curreny mismtach",
});
bankBalance = parseFloat(bankAccount.dataValues.balance);
let overdraftBalance = parseFloat(amount) + parseFloat(bankBalance);
// update opening balance
openingBalance = await OpeningBalance.findOne({
where: {
createdAt: {
[Op.gt]: YESTERDAY_START,
[Op.lt]: YESTERDAY_END,
},
},
});
//update bankAccount
bankAccount = await bankAccount.update({
overdraftBalance: overdraftBalance,
});
if (!bankAccount)
return reply.code(500).send({
status: false,
message: "Unable to update bank account",
});
let res = await BankAccount.findAll({
where: {
createdAt: {
[Op.gt]: YESTERDAY_START,
[Op.lt]: YESTERDAY_END,
},
},
});
let ngnBalance = 0;
let usdBalance = 0;
res.map(async function (res) {
if (res.dataValues.currency === "USD") {
usdBalance += parseFloat(res.dataValues.overdraftBalance);
}
if (res.dataValues.currency === "NGN") {
ngnBalance += parseFloat(res.dataValues.overdraftBalance);
}
});
openingBalance = await openingBalance.update({
dollar: usdBalance,
naira: ngnBalance,
});
if (!openingBalance)
return reply.code(500).send({
status: false,
message: "Unable to update opening balance",
});
statusCode = 201;
result = {
status: true,
message: "Overdraft account created successfully",
};
} catch (e) {
statusCode = e.response.status;
result = {
status: false,
message: e.response.data.message,
};
}
return reply.status(statusCode).send(result);
};
const updateOverdraftHandler = async (req, reply) => {
const YESTERDAY_START = moment().subtract(1, "days").startOf("day").format();
const YESTERDAY_END = moment().subtract(1, "days").endOf("day").format();
const id = req.params.id;
const { amount } = req.body;
try {
let openingBalance;
let overdraft = await Overdraft.findOne({
where: { accountId: id },
});
if (!overdraft)
return reply.code(500).send({
status: false,
message: "Overdraft account not found",
});
let bankAccount = await BankAccount.findOne({
where: {
accountId: overdraft.accountId,
createdAt: {
[Op.gt]: YESTERDAY_START,
[Op.lt]: YESTERDAY_END,
},
},
});
if (!bankAccount)
return reply.code(404).send({
status: false,
message: "Bank account not found",
});
// update opening balance
openingBalance = await OpeningBalance.findOne({
where: {
createdAt: {
[Op.gt]: YESTERDAY_START,
[Op.lt]: YESTERDAY_END,
},
},
});
if (!openingBalance)
return reply.code(404).send({
status: false,
message: "Opening Balance not found",
});
let overdraftDiff;
if (parseFloat(amount) > parseFloat(overdraft.dataValues.amount)) {
overdraftDiff =
parseFloat(amount) - parseFloat(overdraft.dataValues.amount);
overdraftBalance =
parseFloat(bankAccount.dataValues.overdraftBalance) +
parseFloat(overdraftDiff);
if (bankAccount.dataValues.currency === "NGN") {
dollar = openingBalance.dataValues.dollar;
naira =
parseFloat(openingBalance.dataValues.naira) +
parseFloat(overdraftDiff);
}
if (bankAccount.dataValues.currency === "USD") {
naira = openingBalance.dataValues.naira;
dollar =
parseFloat(openingBalance.dataValues.dollar) +
parseFloat(overdraftDiff);
}
bankAccount = await bankAccount.update({
overdraftBalance: overdraftBalance,
});
openingBalance = await openingBalance.update({
dollar,
naira,
});
} else {
overdraftDiff =
parseFloat(amount) - parseFloat(overdraft.dataValues.amount);
overdraftBalance =
parseFloat(bankAccount.dataValues.overdraftBalance) +
parseFloat(overdraftDiff);
bankAccount = await bankAccount.update({
overdraftBalance: overdraftBalance,
});
if (!bankAccount)
return reply.code(500).send({
status: false,
message: "Unable to update bank account",
});
let res = await BankAccount.findAll({
where: {
createdAt: {
[Op.gt]: YESTERDAY_START,
[Op.lt]: YESTERDAY_END,
},
},
});
let ngnBalance = 0;
let usdBalance = 0;
res.map(async function (res) {
if (res.dataValues.currency === "USD") {
usdBalance += parseFloat(res.dataValues.overdraftBalance);
}
if (res.dataValues.currency === "NGN") {
ngnBalance += parseFloat(res.dataValues.overdraftBalance);
}
});
openingBalance = await openingBalance.update({
dollar: usdBalance,
naira: ngnBalance,
});
if (!openingBalance)
return reply.code(500).send({
status: false,
message: "Unable to update opening balance",
});
}
overdraft = await overdraft.update({
amount: amount,
});
if (!overdraft)
return reply.code(500).send({
status: false,
message: "Unable to update overdraft",
});
statusCode = 200;
result = {
status: true,
message: "Overdraft account updated successfully",
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
const deleteOverdraftHandler = async (req, reply) => {
const YESTERDAY_START = moment().subtract(1, "days").startOf("day").format();
const YESTERDAY_END = moment().subtract(1, "days").endOf("day").format();
const id = req.params.id;
try {
let overdraft = await Overdraft.findOne({
where: { accountId: id },
});
if (!overdraft)
return reply.code(500).send({
status: false,
message: "Overdraft account not found",
});
let bankAccount = await BankAccount.findOne({
where: {
accountId: overdraft.accountId,
createdAt: {
[Op.gt]: YESTERDAY_START,
[Op.lt]: YESTERDAY_END,
},
},
});
if (!bankAccount)
return reply.code(404).send({
status: false,
message: "Bank account not found",
});
let openingBalance = await OpeningBalance.findOne({
where: {
createdAt: {
[Op.gt]: YESTERDAY_START,
[Op.lt]: YESTERDAY_END,
},
},
});
if (!openingBalance)
return reply.code(404).send({
status: false,
message: "Opening Balance not found",
});
await overdraft.destroy();
bankAccount = await bankAccount.update({
overdraftBalance: bankAccount.dataValues.balance,
});
if (!bankAccount)
return reply.code(500).send({
status: false,
message: "Unable to update bank account",
});
let res = await BankAccount.findAll({
where: {
createdAt: {
[Op.gt]: YESTERDAY_START,
[Op.lt]: YESTERDAY_END,
},
},
});
let ngnBalance = 0;
let usdBalance = 0;
res.map(async function (res) {
if (res.dataValues.currency === "USD") {
usdBalance += parseFloat(res.dataValues.overdraftBalance);
}
if (res.dataValues.currency === "NGN") {
ngnBalance += parseFloat(res.dataValues.overdraftBalance);
}
});
openingBalance = await openingBalance.update({
dollar: usdBalance,
naira: ngnBalance,
});
if (!openingBalance)
return reply.code(500).send({
status: false,
message: "Unable to update opening balance",
});
statusCode = 200;
result = {
status: true,
message: "Overdraft deleted successfully",
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
const getOverdraftHandler = async (req, reply) => {
try {
let overdrafts = await Overdraft.findAll();
statusCode = 200;
result = {
status: true,
message: "Overdrafts retrieved successfully",
data: overdrafts,
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
const getChartDataHandler = async (req, reply) => {
try {
const { forecastNumber, forecastPeriod } = req.body;
const userId = req.user.id;
const TODAY_START = moment().startOf("day").format();
const TODAY_END = moment().endOf("day").format();
let payload = {
userId: userId,
forecastNumber: forecastNumber,
forecastPeriod: forecastPeriod,
today_start: TODAY_START,
today_end: TODAY_END,
};
const forecastMonths = [];
const forecastNairaChartInflow = [];
const forecastNairaChartOutflow = [];
const forecastDollarChartInflow = [];
const forecastDollarChartOutflow = [];
let purchaseForecasts = await fetchAllPurchaseForecast({ payload });
let saleForecasts = await fetchAllSaleForecast({ payload });
// get invoice forecast where forecastType
let invoiceForecasts = await fetchAllInvoiceForecast({ payload });
let billForecasts = await fetchAllBillForecast({ payload });
for (let i = 0; i < forecastNumber; i++) {
const month = moment().add(i, "months").format("MMMM");
forecastMonths.push(month);
}
for (i = 0; i < invoiceForecasts.rows.length; i++) {
//INFLOW TOTAL
let invoiceForeacastClosingBalance =
invoiceForecasts.rows[i].currency === "NGN"
? invoiceForecasts.rows[i].nairaClosingBalance
: invoiceForecasts.rows[i].dollarClosingBalance;
let saleForecastClosingBalance =
saleForecasts.rows[i].currency === "NGN"
? saleForecasts.rows[i].nairaClosingBalance
: saleForecasts.rows[i].dollarClosingBalance;
// OUTFLOW TOTAL
let billForeacastClosingBalance =
billForecasts.rows[i].currency === "NGN"
? billForecasts.rows[i].nairaClosingBalance
: billForecasts.rows[i].dollarClosingBalance;
let purchaseForecastClosingBalance =
purchaseForecasts.rows[i].currency === "NGN"
? purchaseForecasts.rows[i].nairaClosingBalance
: purchaseForecasts.rows[i].dollarClosingBalance;
if (invoiceForecasts.rows[i].currency === "NGN") {
forecastNairaChartInflow.push(
(parseFloat(invoiceForeacastClosingBalance) +
parseFloat(saleForecastClosingBalance)).toFixed(2)
);
forecastNairaChartOutflow.push(
(parseFloat(billForeacastClosingBalance) +
parseFloat(purchaseForecastClosingBalance)).toFixed(2)
);
} else {
forecastDollarChartInflow.push(
(parseFloat(invoiceForeacastClosingBalance) +
parseFloat(saleForecastClosingBalance)).toFixed(2)
);
forecastDollarChartOutflow.push(
(parseFloat(billForeacastClosingBalance) +
parseFloat(purchaseForecastClosingBalance)).toFixed(2)
);
}
}
statusCode = 200;
result = {
status: true,
data: {
"months": forecastMonths,
"forecastNairaInflow": forecastNairaChartInflow,
"forecastNairaOutflow": forecastNairaChartOutflow,
"forecastDollarInflow": forecastDollarChartInflow,
"forecastDollarOutflow": forecastDollarChartOutflow,
},
};
} catch (e) {
statusCode = e.response.status;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
module.exports = {
resyncHandler,
bankAccountsHandler,
createOpeningBalanceHandler,
createOverdraftHandler,
updateOverdraftHandler,
deleteOverdraftHandler,
getOverdraftHandler,
getChartDataHandler,
downloadOpeningBalance,
PrepaidExpHandler,
ACcruedExpHandler,
};

View File

@ -0,0 +1,7 @@
//getInvoice
// getCustomerPayments
// getSalesOrder
//processSales

View File

@ -0,0 +1,7 @@
// getBill
// getVendorPayments
// getPurchaseOrder
// processPurchases

View File

@ -0,0 +1,190 @@
const { default: axios } = require('axios');
const db = require('../../models');
const User = db.users;
const Token = db.tokens;
const dayjs = require('dayjs');
var utc = require('dayjs/plugin/utc');
var timezone = require('dayjs/plugin/timezone');
const config = require('../../../config');
dayjs.extend(utc);
dayjs.extend(timezone);
// dayjs.tz.setDefault("Africa/Lagos")
Date.prototype.addHours = function (h) {
this.setHours(this.getHours() + h);
return this;
};
const options = {
headers: { 'Content-Type': ['application/json'] },
};
// this generates zoho access token
const generateZohoTokenHandler = async (req, reply) => {
const { code } = req.body;
//console.log(code,">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
try {
let user;
let resp;
let url;
// get current user
user = await User.findOne({
where: { email: req.user.email },
});
//console.log(user,2222222222222222222222222222222222222222222222222222222222222)
if (!user) {
return reply.code(404).send({
status: false,
message: 'User Not Found',
});
}
// check if user has zoho token
let zohoToken = await Token.findOne({
where: {
userId: user.id,
},
});
console.log(zohoToken,2222222222222222222222222222222222222222222222222222222222222)
// new to zoho
if (!zohoToken) {
if (!code) {
return reply.code(400).send({
status: false,
message: 'Code is required',
});
}
url = `${config.ZOHO_BASE_URL}?code=${code}&client_id=${config.ZOHO_CLIENT_ID}&client_secret=${config.ZOHO_CLIENT_SECRET}&redirect_uri=${config.BASE_URL}&grant_type=authorization_code`;
console.log(url,44444444444444444444444444444444444444444444444444444444444444444444444)
resp = await axios.post(url, options);
console.log(resp,44444444444444444444444444444444444444444444444444444444444444444444444)
if (resp.data.error)
return reply.code(400).send({
status: false,
message: 'Invalid code',
});
// store the zoho token in the zoho table
zohoToken = await Token.create({
userId: user.id,
//zohoAccessToken: resp.data.access_token,
zohoRefreshToken: resp.data.refresh_token,
zohoTokenExpiry: dayjs().add(3600, 'second').utc(1).format(),
zohoTokenDate: new Date().addHours(1),
});
} else {
url = `${config.ZOHO_BASE_URL}?refresh_token=1000.b19eafa36c01c36f065d567e6dead718.5f0ee96b622ad2a7d1a1dcd7169f5f31&client_id=${config.ZOHO_CLIENT_ID}&client_secret=${config.ZOHO_CLIENT_SECRET}&redirect_uri=${config.ZOHO_REDIRECT_URI}&grant_type=refresh_token`;
console.log(url, 'refresh tokeeennnnnnnnn22222222222222222222222222222222')
resp = await axios.post(url, options);
if (resp.data.error) {
return reply.code(400).send({
status: false,
message: 'Invalid code',
});
}
zohoToken = await zohoToken.update({
// zohoAccessToken: resp.data.access_token,
zohoTokenExpiry: dayjs().add(3600, 'second').utc(1).format(),
zohoTokenDate: new Date().addHours(1),
});
console.log(zohoToken, 'zohoToken tokeeennnnnnnnn22222222222222222222222222222222')
}
user.update({
isZohoAuthenticated: true,
});
statusCode = 200;
result = {
status: true,
message: 'Zoho Token generated successfully',
data: {
zohoAccessToken: resp.data.access_token,
zohoTokenExpiry: zohoToken.zohoTokenExpiry,
},
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
const refreshZohoTokenHandler = async (req, reply) => {
try {
// get current user
let user = await User.findOne({
where: { email: req.user.email },
});
if (!user) {
return reply.code(404).send({
status: false,
message: 'User Not Found',
});
}
// check if user has zoho token
let zohoToken = await Token.findOne({
where: {
userId: user.id,
},
});
if (!zohoToken) {
return reply.code(401).send({
status: false,
message: 'Unauthorized access to zoho',
});
}
url = `${config.ZOHO_BASE_URL}?refresh_token=${zohoToken.zohoRefreshToken}&client_id=${config.ZOHO_CLIENT_ID}&client_secret=${config.ZOHO_CLIENT_SECRET}&redirect_uri=${config.ZOHO_REDIRECT_URI}&grant_type=refresh_token`;
resp = await axios.post(url, options);
if (resp.data.error) {
return reply.code(400).send({
status: false,
message: 'Invalid code',
});
}
zohoToken = await zohoToken.update({
// zohoAccessToken: resp.data.access_token,
zohoTokenExpiry: dayjs().add(3600, 'second').utc(1).format(),
zohoTokenDate: new Date().addHours(1),
});
statusCode = 200;
result = {
status: true,
message: 'Zoho Token generated successfully',
data: {
zohoAccessToken: resp.data.access_token,
zohoTokenExpiry: zohoToken.zohoTokenExpiry,
},
};
} catch (e) {
statusCode = e.code;
result = {
status: false,
message: e.message,
};
}
return reply.status(statusCode).send(result);
};
module.exports = { generateZohoTokenHandler, refreshZohoTokenHandler };

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,106 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="x-apple-disable-message-reformatting">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Overlock:ital,wght@0,400;0,700;0,900;1,400;1,700;1,900&display=swap"
rel="stylesheet">
<title></title>
<style>
table,
td,
div,
h1,
p {
font-family: Arial, sans-serif;
}
</style>
</head>
<body style="margin:0;padding:0;">
<table role="presentation"
style="width:100%;border-collapse:collapse;border:0;border-spacing:0;background:#ffffff;">
<tr>
<td align="center" style="padding:0;">
<table role="presentation"
style="width:702px;border-collapse:collapse;border:1px solid #cccccc;border-spacing:0;text-align:left;">
<tr>
<td align="center" style="background:#fff;">
<img src="https://manifoldcomputers.com/wp-content/uploads/2020/06/Manifold-Logo.png" alt="" width="30%" style="height:auto;display:block;" />
</td>
</tr>
<tr>
<td style="padding:36px 70px 42px 70px;">
<table role="presentation"
style="width:100%;border-collapse:collapse;border:0;border-spacing:0;">
<tr>
<td style="padding:0 0 26px 0;color:#153643;">
<h4 style="margin:0 0 20px 0;font-family: 'Overlock';">Hello, </h1>
<p
style="margin:0 0 12px 0;font-size:14px;line-height:24px;font-family: 'Overlock';">
Congratulations!!! You have being invited to complete the registration
process for Manifold Computers Limited Forecasting Application.</p>
<p
style="margin:0 0 12px 0;font-size:14px;line-height:24px;font-family: 'Overlock';">
You need to click on the link below to complete your registration
process. The link will expire in the next 24 hours</p>
<p
style="margin:0 0 12px 0;font-size:14px;line-height:24px;font-family: 'Overlock';">
Thank you,<br>Manifold Computers Limited</p>
</td>
</table>
</td>
</tr>
<tr>
<td style="padding:10px 0;background:#f9f9f9; text-align: center; width: 100%;">
<table role="presentation"
style="width:100%;border-collapse:collapse;border:0;border-spacing:0;font-size:9px;font-family:Arial,sans-serif;">
<tr>
<td>
<p
style="margin:0 0 12px 0;font-size:14px;line-height:24px;font-family: 'Overlock';">
Complete registration by clicking <a href="{{ url }}"
style="color:red;">here</a></p>
</td>
</tr>
<tr
style="width:80%;text-align:center;margin:0 auto;height:1rem;border-collapse:collapse;border:0;border-spacing:0;font-size:9px;font-family:Arial,sans-serif;background-color: #fff;display: flex; justify-content: center;">
<td></td>
</tr>
<tr>
<td>
<p
style="font-size:14px; font-weight: bold;font-family: 'Overlock';margin-top: 2rem;">
<a href="https://manifoldcomputers.com"
style="color:#000;text-decoration:none;border-top: 1px solid rgba(0, 0, 0, 0.24); padding-top: .5rem;">Manifold
Computers Limited</a>
</p>
</td>
</tr>
<tr>
<td>
<p style="font-size:11px; font-weight: bold;font-family: 'Overlock'">Get the
most of Manifold Computer Limited Forecasting Application by running it on
the web platfrom.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

BIN
src/emailTemplates/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,147 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="x-apple-disable-message-reformatting">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Overlock:ital,wght@0,400;0,700;0,900;1,400;1,700;1,900&display=swap"
rel="stylesheet">
<title></title>
<style>
table,
td,
div,
h1,
p {
font-family: Arial, sans-serif;
}
</style>
</head>
<body style="margin:0;padding:0;">
<table role="presentation"
style="width:100%;border-collapse:collapse;border:0;border-spacing:0;background:#ffffff;">
<tr>
<td align="center" style="padding:0;">
<table role="presentation"
style="width:702px;border-collapse:collapse;border:1px solid #cccccc;border-spacing:0;text-align:left;">
<tr>
<td align="center" style="background:#fff;">
<img src="https://manifoldcomputers.com/wp-content/uploads/2020/06/Manifold-Logo.png" alt=""
width="30%" style="height:auto;display:block;" />
</td>
</tr>
<tr>
<td style="padding:36px 70px 42px 70px;">
<table role="presentation"
style="width:100%;border-collapse:collapse;border:0;border-spacing:0;">
<tr>
<td style="padding:0 0 26px 0;color:#153643;">
<h4 style="margin:0 0 20px 0;font-family: 'Overlock'">Hello {{username}} </h1>
<p
style="margin:0 0 12px 0;font-size:16px;line-height:24px;font-family: 'Overlock'">
The password for your Manifold Computers Limited Forecasting Application
(email ) has been successfully reset.
</p>
<p
style="margin:0 0 12px 0;font-size:16px;line-height:24px;font-family: 'Overlock';">
Update your password by clicking on this link: <a href="{{ url }}"
style="color:red;">Reset
Password</a>
</p>
<p
style="margin:0 0 12px 0;font-size:16px;line-height:24px;font-family: 'Overlock'">
If you did not make this change or you believe an unauthorized person
has accessed your account,
go to <a href="https://www.manifoldcomputers.com"
style="color:royalblue;">[https://www.manifoldcomputers.com]</a> to
reset your password without
delay.</p>
<p
style="margin:0 0 12px 0;font-size:16px;line-height:24px;font-family: 'Overlock'">
Following this, sign in to your Forecasting Application account page at
<a href="https://www.manifoldcomputers.com"
style="color:royalblue;">[https://www.manifoldcomputers.com]</a> to
review
and update your security
settings.
If you need additional help, please contact Manifold Computers Limited
Support.
</p>
<p
style="margin:0 0 12px 0;font-size:16px;line-height:24px;font-family: 'Overlock'">
Thanks,
<br>Manifold Computers Limited
</p>
</td>
</table>
</td>
</tr>
<tr>
<td style="padding:10px 0;background:#f9f9f9; text-align: center; width: 100%;">
<table role="presentation"
style="width:100%;border-collapse:collapse;border:0;border-spacing:0;font-size:9px;font-family:Arial,sans-serif;">
<tr>
<td>
<p
style="margin:0 0 12px 0;font-size:14px;line-height:24px;font-family: 'Overlock';">
Update your password by clicking on this link: <a
href="https://www.manifoldcomputerscom" style="color:red;">Reset
Password</a:href>
</p>
</td>
</tr>
<tr
style="width:80%;text-align:center;margin:0 auto;height:1rem;border-collapse:collapse;border:0;border-spacing:0;font-size:9px;font-family:Arial,sans-serif;background-color: #fff;display: flex; justify-content: center;">
<td></td>
</tr>
<tr>
<td>
<p
style="font-size:14px; font-weight: bold;font-family: 'Overlock';margin-top: 2rem;">
<a href="https://manifoldcomputers.com"
style="color:#000;text-decoration:none;border-top: 1px solid rgba(0, 0, 0, 0.24); padding-top: .5rem;">Manifold
Computers Limited</a>
</p>
</td>
</tr>
<tr>
<td>
<p style="font-size:11px; font-weight: bold;font-family: 'Overlock'">Get the
most of Manifold Computer Limited Forecasting Application by running it on
the web platfrom.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,108 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="x-apple-disable-message-reformatting">
<link
href="https://fonts.googleapis.com/css2?family=Overlock:ital,wght@0,400;0,700;0,900;1,400;1,700;1,900&display=swap"
rel="stylesheet">
<title></title>
<style>
table,
td,
div,
h1,
p {
font-family: Arial, sans-serif;
}
</style>
</head>
<body style="margin:0;padding:0;">
<table role="presentation"
style="width:100%;border-collapse:collapse;border:0;border-spacing:0;background:#ffffff;">
<tr>
<td align="center" style="padding:0;">
<table role="presentation"
style="width:702px;border-collapse:collapse;border:1px solid #cccccc;border-spacing:0;text-align:left;">
<tr>
<td align="center" style="background:#fff;">
<img src="https://manifoldcomputers.com/wp-content/uploads/2020/06/Manifold-Logo.png" alt=""
width="30%" style="height:auto;display:block;" />
</td>
</tr>
<tr>
<td style="padding:36px 70px 42px 70px;">
<table role="presentation"
style="width:100%;border-collapse:collapse;border:0;border-spacing:0;">
<tr>
<td style="padding:0 0 26px 0;color:#153643;">
<h4 style="margin:0 0 20px 0;font-family: 'Overlock'">Hello {{username}} </h1>
<p> </p>
<h3 style="font-family: 'Overlock'">Welcome to the <span
style="color:red;">Manifold Forecasting Application</span></h3>
<p
style="margin:0 0 12px 0;font-size:16px;line-height:24px;font-family: 'Overlock'">
<b>Manifold Computers Limited Forecasting Application</b> allow you to
.....
</p>
</td>
</table>
</td>
</tr>
<tr>
<td style="padding:10px 0;background:#f9f9f9; text-align: center; width: 100%;">
<table role="presentation"
style="width:100%;border-collapse:collapse;border:0;border-spacing:0;font-size:9px;font-family:Arial,sans-serif;">
<tr>
<td>
<p
style="margin:0 0 12px 0;font-size:14px;line-height:24px;font-family: 'Overlock';">
<a href="https://www.manifoldcomputers.com" style="color:red;">
<h3>Click here to get started</h3>
</a>
</p>
<p
style="margin:0 0 12px 0;font-size:14px;line-height:24px;font-family: 'Overlock';">
You can access Manifold computers limited forecasting applicaiton or on any
device by going to <a href="https://www.manifoldcomputers.com"
style="color:red;">https://www.manifoldcomputers.com</a:href>
</p>
</td>
</tr>
<tr
style="width:80%;text-align:center;margin:0 auto;height:1rem;border-collapse:collapse;border:0;border-spacing:0;font-size:9px;font-family:Arial,sans-serif;background-color: #fff;display: flex; justify-content: center;">
<td></td>
</tr>
<tr>
<td>
<p
style="font-size:14px; font-weight: bold;font-family: 'Overlock';margin-top: 2rem;">
<a href="https://manifoldcomputers.com"
style="color:#000;text-decoration:none;border-top: 1px solid rgba(0, 0, 0, 0.24); padding-top: .5rem;">Manifold
Computers Limited</a>
</p>
</td>
</tr>
<tr>
<td>
<p style="font-size:11px; font-weight: bold;font-family: 'Overlock'">Get the
most of Manifold Computer Limited Forecasting Application by running it on
the web platfrom.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

399
src/helpers/dbQuery.js Normal file
View File

@ -0,0 +1,399 @@
const { Op } = require('sequelize');
const db = require('../models');
const InvoiceForecast = db.invoiceForecasts;
const PurchaseForecast = db.purchaseForecasts;
const SaleForecast = db.saleForecasts;
const BillForecast = db.billForecasts;
const ZohoRate = db.zohorates;
const VendorPayment = db.vendorPayments;
const CustomerPayment = db.customerPayments;
const OpeningBalance = db.openingBalances;
const BankAccount = db.bankAccounts;
const accrued = db.accruedexp;
const prepaidExpense = db.prepaidexp;
const Invoice = db.invoices;
const Bill = db.bills;
const Purchase = db.purchases;
const Sale = db.sales;
const InitialBalance = db.initialBalances;
const createInvoiceForecast = async (
userId,
naira,
dollar,
month,
forecastNumber,
forecastPeriod,
currency
) => {
await InvoiceForecast.create({
userId: userId,
nairaClosingBalance: naira,
dollarClosingBalance: dollar,
month: month,
forecastType: `${forecastNumber} ${forecastPeriod}`,
currency: currency,
});
};
const createBillForecast = async (
userId,
naira,
dollar,
month,
forecastNumber,
forecastPeriod,
currency
) => {
await BillForecast.create({
userId: userId,
nairaClosingBalance: naira,
dollarClosingBalance: dollar,
month: month,
forecastType: `${forecastNumber} ${forecastPeriod}`,
currency: currency,
});
};
const createPurchaseForecast = async (
userId,
naira,
dollar,
month,
forecastNumber,
forecastPeriod,
currency
) => {
await PurchaseForecast.create({
userId: userId,
nairaClosingBalance: naira,
dollarClosingBalance: dollar,
month: month,
forecastType: `${forecastNumber} ${forecastPeriod}`,
currency: currency,
});
};
const createSaleForecast = async (
userId,
naira,
dollar,
month,
forecastNumber,
forecastPeriod,
currency
) => {
await SaleForecast.create({
userId: userId,
nairaClosingBalance: naira,
dollarClosingBalance: dollar,
month: month,
forecastType: `${forecastNumber} ${forecastPeriod}`,
currency: currency,
});
};
const createInvoice = async ({ payload }) => {
await Invoice.create(payload);
};
const createBill = async ({ payload }) => {
await Bill.create(payload);
};
const createPurchase = async ({ payload }) => {
await Purchase.create(payload);
};
const createSale = async ({ payload }) => {
await Sale.create(payload);
};
const fetchAllInvoice = async ({ payload }) => {
return await Invoice.findAndCountAll({
where: {
userId: payload.userId,
forecastType: `${payload.forecastNumber} ${payload.forecastPeriod}`,
createdAt: {
[Op.gt]: payload.today_start,
[Op.lt]: payload.today_end,
},
},
});
};
const fetchAllBill = async ({ payload }) => {
return await Bill.findAndCountAll({
where: {
userId: payload.userId,
forecastType: `${payload.forecastNumber} ${payload.forecastPeriod}`,
createdAt: {
[Op.gt]: payload.today_start,
[Op.lt]: payload.today_end,
},
},
});
};
const fetchAllPurchase = async ({ payload }) => {
return await Purchase.findAndCountAll({
where: {
userId: payload.userId,
forecastType: `${payload.forecastNumber} ${payload.forecastPeriod}`,
createdAt: {
[Op.gt]: payload.today_start,
[Op.lt]: payload.today_end,
},
},
});
};
const fetchAllSale = async ({ payload }) => {
return await Sale.findAndCountAll({
where: {
userId: payload.userId,
forecastType: `${payload.forecastNumber} ${payload.forecastPeriod}`,
createdAt: {
[Op.gt]: payload.today_start,
[Op.lt]: payload.today_end,
},
},
});
};
const getInitialBalance = async ({ payload }) => {
return await InitialBalance.findOne({
where: {
userId: payload.userId,
forecastType: `${payload.forecastNumber} ${payload.forecastPeriod}`,
createdAt: {
[Op.gt]: payload.today_start,
[Op.lt]: payload.today_end,
},
},
});
};
const fetchAllInvoiceForecast = async ({ payload }) => {
return await InvoiceForecast.findAndCountAll({
where: {
userId: payload.userId,
forecastType: `${payload.forecastNumber} ${payload.forecastPeriod}`,
createdAt: {
[Op.gt]: payload.today_start,
[Op.lt]: payload.today_end,
},
},
});
};
const fetchAllBillForecast = async ({ payload }) => {
return await BillForecast.findAndCountAll({
where: {
userId: payload.userId,
forecastType: `${payload.forecastNumber} ${payload.forecastPeriod}`,
createdAt: {
[Op.gt]: payload.today_start,
[Op.lt]: payload.today_end,
},
},
});
};
const fetchAllPurchaseForecast = async ({ payload }) => {
return await PurchaseForecast.findAndCountAll({
where: {
userId: payload.userId,
forecastType: `${payload.forecastNumber} ${payload.forecastPeriod}`,
createdAt: {
[Op.gt]: payload.today_start,
[Op.lt]: payload.today_end,
},
},
});
};
const fetchAllSaleForecast = async ({ payload }) => {
return await SaleForecast.findAndCountAll({
where: {
userId: payload.userId,
forecastType: `${payload.forecastNumber} ${payload.forecastPeriod}`,
createdAt: {
[Op.gt]: payload.today_start,
[Op.lt]: payload.today_end,
},
},
});
};
const getPurchaseForecast = async ({ payload }) => {
return await PurchaseForecast.findOne({
where: {
userId: payload.userId,
forecastType: payload.forecastType,
currency: payload.currency,
createdAt: {
[Op.gt]: payload.today_start,
[Op.lt]: payload.today_end,
},
},
});
};
const getSaleForecast = async ({ payload }) => {
return await SaleForecast.findOne({
where: {
userId: payload.userId,
forecastType: `${payload.forecastNumber} ${payload.forecastPeriod}`,
currency: payload.currency,
createdAt: {
[Op.gt]: payload.today_start,
[Op.lt]: payload.today_end,
},
},
});
};
const createVendorPayment = async ({ payload }) => {
return await VendorPayment.create(payload);
};
const createCustomerPayment = async ({ payload }) => {
return await CustomerPayment.create(payload);
};
const getVendorPaymentByVendorId = async ({ payload }) => {
return await VendorPayment.findOne({
where: {
userId: payload.userId,
vendorId: payload.vendorId,
forecastType: `${payload.forecastNumber} ${payload.forecastPeriod}`,
currencyCode: payload.currencyCode,
createdAt: {
[Op.gt]: payload.today_start,
[Op.lt]: payload.today_end,
},
},
});
};
const getCustomerPaymentByCustomerId = async ({ payload }) => {
return await CustomerPayment.findOne({
where: {
userId: payload.userId,
customerId: payload.customerId,
forecastType: `${payload.forecastNumber} ${payload.forecastPeriod}`,
currencyCode: payload.currencyCode,
createdAt: {
[Op.gt]: payload.today_start,
[Op.lt]: payload.today_end,
},
},
});
};
const fetchAllVendorPayments = async ({ payload }) => {
return await VendorPayment.findAndCountAll({
where: {
userId: payload.userId,
forecastType: `${payload.forecastNumber} ${payload.forecastPeriod}`,
createdAt: {
[Op.gt]: payload.today_start,
[Op.lt]: payload.today_end,
},
},
});
};
const fetchAllCustomerPayments = async ({ payload }) => {
return await CustomerPayment.findAndCountAll({
where: {
userId: payload.userId,
forecastType: `${payload.forecastNumber} ${payload.forecastPeriod}`,
createdAt: {
[Op.gt]: payload.today_start,
[Op.lt]: payload.today_end,
},
},
});
};
const createInitialBalance = async ({ startingBalance }) => {
return await InitialBalance.create(startingBalance);
};
const createOpeningBalance = async ({ payload }) => {
return await OpeningBalance.create(payload);
};
const createBankAccounts = async ({ bankAccounts }) => {
return await BankAccount.create(bankAccounts);
};
const getTodayDayOpeningBalance = async ({ todayOpeningBalData }) => {
console.log('today', todayOpeningBalData);
return await OpeningBalance.findOne({
where: {
createdAt: {
[Op.gt]: todayOpeningBalData.today_start,
[Op.lt]: todayOpeningBalData.today_end,
},
},
});
};
const getPreviousDayOpeningBalance = async ({ prevOpeningBalData }) => {
console.log('prev', prevOpeningBalData);
return await OpeningBalance.findOne({
where: {
createdAt: {
[Op.gt]: prevOpeningBalData.yesterday_start,
[Op.lt]: prevOpeningBalData.yesterday_end,
},
},
});
};
const createZohoRate = async ({ payload }) => {
return await ZohoRate.create(payload);
};
module.exports = {
createZohoRate,
createInvoiceForecast,
createInvoice,
createBillForecast,
createBill,
createPurchaseForecast,
createPurchase,
createSaleForecast,
createSale,
createVendorPayment,
createCustomerPayment,
fetchAllInvoice,
fetchAllBill,
fetchAllPurchase,
fetchAllSale,
getInitialBalance,
fetchAllInvoiceForecast,
fetchAllBillForecast,
fetchAllPurchaseForecast,
fetchAllSaleForecast,
fetchAllVendorPayments,
fetchAllCustomerPayments,
getVendorPaymentByVendorId,
getCustomerPaymentByCustomerId,
getPurchaseForecast,
getSaleForecast,
createInitialBalance,
createOpeningBalance,
createBankAccounts,
getTodayDayOpeningBalance,
getPreviousDayOpeningBalance,
};

77
src/helpers/email.js Normal file
View File

@ -0,0 +1,77 @@
const nodemailer = require('nodemailer');
const Handlebars = require('handlebars');
const fs = require('fs');
const path = require('path');
const config = require('../../config');
const sendEmailTemplate = async (templateDetails) => {
const { name, templateToUse, url } = templateDetails;
let filePath = '';
let template = '';
try {
//template could be any of the following: invite, passwordChanged.
if (templateToUse == 'invite') {
filePath = path.join(__dirname, '../emailTemplates/index.html');
} else if (templateToUse == 'signup') {
filePath = path.join(
__dirname,
'../emailTemplates/signup_email_template.html'
);
} else if (templateToUse == 'passwordReset') {
filePath = path.join(
__dirname,
'../emailTemplates/password_reset_email_template.html'
);
}
const source = fs.readFileSync(filePath, 'utf-8').toString();
template = Handlebars.compile(source);
const replacements = {
username: name ?? '',
url: url ?? '',
};
const htmlToSend = template(replacements);
return htmlToSend;
} catch (error) {
return error;
}
};
async function sendEmail(to, subject, body, detail) {
try {
const transporter = nodemailer.createTransport({
host: config.SMTP_HOST,
port: config.SMTP_PORT,
secure: false, // upgrade later with STARTTLS
tls: {
ciphers: 'SSLv3',
},
auth: {
user: config.SMTP_USER,
pass: `${config.SMTP_PASS}`,
},
});
const template = await sendEmailTemplate(detail);
const message = {
from: `${config.EMAIL_FROM} "MANIFOLD FORECAST"`,
to: to,
subject: subject,
text: body,
html: template,
};
const result = await transporter.sendMail(message);
return result;
} catch (error) {
return error;
}
}
module.exports = {
sendEmail,
sendEmailTemplate,
};

46
src/helpers/status.js Normal file
View File

@ -0,0 +1,46 @@
const badRequest = {
type: 'object',
description: 'Bad Request',
properties: {
status: { type: 'boolean' },
message: { type: 'string' },
},
};
const internalServer = {
type: 'object',
description: 'Internal Server Error',
properties: {
status: { type: 'boolean' },
message: { type: 'string' },
},
};
const noConent = {
type: 'object',
description: 'No Content',
properties: {
status: { type: 'boolean' },
message: { type: 'string' },
},
};
const success = {
type: 'object',
description: 'OK',
properties: {
status: { type: 'boolean' },
message: { type: 'string' },
data: {},
},
};
const created = {
type: 'object',
description: 'Created',
properties: {
status: { type: 'boolean' },
message: { type: 'string' },
},
};
module.exports = { badRequest, internalServer, noConent, success, created };

View File

@ -0,0 +1,54 @@
module.exports = (sequelize, DataTypes) => {
const AccruedExpense = sequelize.define('accruedexpenses', {
account_id: {
type: DataTypes.STRING,
allowNull: false,
},
tax_id: {
type: DataTypes.STRING,
allowNull: true,
},
payment_date: {
type: DataTypes.DATE,
allowNull: false,
},
is_taxable: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
amount: {
type: DataTypes.DECIMAL(12, 2),
},
// role is true:1 for admin and false:0 for user
reference_number: {
type: DataTypes.STRING,
},
customer_id: {
type: DataTypes.STRING,
defaultValue: false,
},
description: {
type: DataTypes.TEXT('long'),
},
token: {
type: DataTypes.TEXT('long'),
},
currency_id: {
type: DataTypes.STRING,
},
non_billable: {
type: DataTypes.BOOLEAN,
defaultValue: true,
},
paymentConfirmation: {
type: DataTypes.INTEGER,
defaultValue: 0,
},
});
return AccruedExpense;
};

View File

@ -0,0 +1,32 @@
module.exports = (sequelize, DataTypes) => {
const BankAccount = sequelize.define('bankAccount', {
accountId: {
type: DataTypes.STRING,
},
accountName: {
type: DataTypes.STRING,
},
accountType: {
type: DataTypes.STRING,
},
accountNumber: {
type: DataTypes.STRING,
},
bankName: {
type: DataTypes.STRING,
},
currency: {
type: DataTypes.STRING,
},
balance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
overdraftBalance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
});
return BankAccount;
};

View File

@ -0,0 +1,27 @@
module.exports = (sequelize, DataTypes) => {
const BillForecast = sequelize.define('billForecast', {
dollarClosingBalance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
nairaClosingBalance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
month: {
type: DataTypes.STRING,
},
forecastType: {
type: DataTypes.STRING,
}, // 1 day, 1 week, 1month, 2month etc
currency: {
type: DataTypes.STRING,
},
userId: {
type: DataTypes.STRING,
allowNull: false,
},
});
return BillForecast;
};

56
src/models/bill.model.js Normal file
View File

@ -0,0 +1,56 @@
module.exports = (sequelize, DataTypes) => {
const Bill = sequelize.define(' bill', {
billId: {
type: DataTypes.STRING,
},
vendorId: {
type: DataTypes.STRING,
},
vendorName: {
type: DataTypes.STRING,
},
status: {
type: DataTypes.STRING,
},
invoiceNumber: {
type: DataTypes.STRING,
},
refrenceNumber: {
type: DataTypes.STRING,
},
date: {
type: DataTypes.STRING,
},
dueDate: {
type: DataTypes.STRING,
},
currencyCode: {
type: DataTypes.STRING,
},
balance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
naira: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
dollar: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
exchangeRate: {
type: DataTypes.DECIMAL(10, 6),
defaultValue: 0.0,
},
forecastType: {
type: DataTypes.STRING,
},
userId: {
type: DataTypes.STRING,
allowNull: false,
},
});
return Bill;
};

View File

@ -0,0 +1,51 @@
module.exports = (sequelize, DataTypes) => {
const CustomerPayment = sequelize.define('customerPayment', {
paymentId: {
type: DataTypes.STRING,
},
customerId: {
type: DataTypes.STRING,
},
customerName: {
type: DataTypes.STRING,
},
invoiceNumbers: {
type: DataTypes.STRING,
},
refrenceNumber: {
type: DataTypes.STRING,
},
date: {
type: DataTypes.STRING,
},
currencyCode: {
type: DataTypes.STRING,
},
amount: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
// Amount after deducting from purchase order
balance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
saleForcastbalance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
exchangeRate: {
type: DataTypes.DECIMAL(10, 6),
defaultValue: 0.0,
},
forecastType: {
type: DataTypes.STRING,
},
userId: {
type: DataTypes.STRING,
allowNull: false,
},
});
return CustomerPayment;
};

71
src/models/index.js Normal file
View File

@ -0,0 +1,71 @@
require('dotenv').config();
const Sequelize = require('sequelize');
const config = require('../../config');
let sequelize;
sequelize = new Sequelize(
`${config.MYSQL_DATABASE}`,
`${config.MYSQL_USERNAME}`,
config.MYSQL_ROOT_PASSWORD,
{
host: config.MYSQL_HOST,
port: config.MYSQL_DOCKER_PORT,
dialect: 'mysql',
operatorsAliases: 0,
timezone: '+01:00',
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000,
},
}
);
const db = {};
db.Sequelize = Sequelize;
db.sequelize = sequelize;
db.users = require('./user.model')(sequelize, Sequelize);
db.prepaidexp = require('./prepaideExpenses.forecast.model')(sequelize, Sequelize);
db.accruedexp = require('./accruedExpenses.forecast.model')(sequelize, Sequelize);
db.tokens = require('./token.model')(sequelize, Sequelize);
db.rates = require('./rate.model')(sequelize, Sequelize);
db.zohorates = require('./zoho.rate.model')(sequelize, Sequelize);
db.invoices = require('./invoice.model')(sequelize, Sequelize);
db.invoiceForecasts = require('./invoice.forecast.model')(sequelize, Sequelize);
db.purchases = require('./purchase.model')(sequelize, Sequelize);
db.purchaseForecasts = require('./purchase.forecast.model')(
sequelize,
Sequelize
);
db.sales = require('./sale.model')(sequelize, Sequelize);
db.saleForecasts = require('./sale.forecast.model')(sequelize, Sequelize);
db.bills = require('./bill.model')(sequelize, Sequelize);
db.billForecasts = require('./bill.forecast.model')(sequelize, Sequelize);
db.initialBalances = require('./initial.balance.model')(sequelize, Sequelize);
db.customerPayments = require('./customer.payment.model')(sequelize, Sequelize);
db.vendorPayments = require('./vendor.payment.model')(sequelize, Sequelize);
db.openingBalances = require('./opening.balance.model')(sequelize, Sequelize);
db.bankAccounts = require('./bank.account.model')(sequelize, Sequelize);
db.overdrafts = require('./overdraft.model')(sequelize, Sequelize);
db.tokens.belongsTo(db.users, { foreignKey: 'userId' });
db.users.hasMany(db.tokens, { foreignKey: 'userId' });
sequelize
.sync({ force: false, alter: true })
.then(async () => {
// await db.roles.create({
// name: "Admin",
// description: "The admin",
// });
})
.catch((err) => {
console.log(err);
});
module.exports = db;

View File

@ -0,0 +1,29 @@
module.exports = (sequelize, DataTypes) => {
const InitialBalance = sequelize.define('initialBalance', {
openingBalance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
nairaOpeningBalance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
dollarOpeningBalance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
rate: {
type: DataTypes.DECIMAL(10, 2),
defaultValue: 0.0,
},
forecastType: {
type: DataTypes.STRING,
}, // 1 day, 1 week, 1month, 2month etc,
userId: {
type: DataTypes.STRING,
allowNull: false,
},
});
return InitialBalance;
};

View File

@ -0,0 +1,27 @@
module.exports = (sequelize, DataTypes) => {
const InvoiceForecast = sequelize.define('invoiceForecast', {
dollarClosingBalance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
nairaClosingBalance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
month: {
type: DataTypes.STRING,
},
forecastType: {
type: DataTypes.STRING,
}, // 1 day, 1 week, 1month, 2month etc,
currency: {
type: DataTypes.STRING,
},
userId: {
type: DataTypes.STRING,
allowNull: false,
},
});
return InvoiceForecast;
};

View File

@ -0,0 +1,53 @@
module.exports = (sequelize, DataTypes) => {
const Invoice = sequelize.define('invoice', {
invoiceId: {
type: DataTypes.STRING,
},
customerName: {
type: DataTypes.STRING,
},
status: {
type: DataTypes.STRING,
},
invoiceNumber: {
type: DataTypes.STRING,
},
refrenceNumber: {
type: DataTypes.STRING,
},
date: {
type: DataTypes.STRING,
},
dueDate: {
type: DataTypes.STRING,
},
currencyCode: {
type: DataTypes.STRING,
},
balance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
naira: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
dollar: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
exchangeRate: {
type: DataTypes.DECIMAL(10, 6),
defaultValue: 0.0,
},
forecastType: {
type: DataTypes.STRING,
},
userId: {
type: DataTypes.STRING,
allowNull: false,
},
});
return Invoice;
};

View File

@ -0,0 +1,14 @@
module.exports = (sequelize, DataTypes) => {
const OpeningBalance = sequelize.define('openingBalance', {
naira: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
dollar: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
});
return OpeningBalance;
};

View File

@ -0,0 +1,28 @@
module.exports = (sequelize, DataTypes) => {
const Overdraft = sequelize.define('overdraft', {
accountId: {
type: DataTypes.STRING,
},
accountName: {
type: DataTypes.STRING,
},
accountType: {
type: DataTypes.STRING,
},
accountNumber: {
type: DataTypes.STRING,
},
bankName: {
type: DataTypes.STRING,
},
currency: {
type: DataTypes.STRING,
},
amount: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
});
return Overdraft;
};

View File

@ -0,0 +1,54 @@
module.exports = (sequelize, DataTypes) => {
const PrepaidExpense = sequelize.define('prepaidexpenses', {
account_id: {
type: DataTypes.STRING,
allowNull: false,
},
tax_id: {
type: DataTypes.STRING,
allowNull: true,
},
payment_date: {
type: DataTypes.DATE,
allowNull: false,
},
is_taxable: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
amount: {
type: DataTypes.DECIMAL(12, 2),
},
// role is true:1 for admin and false:0 for user
reference_number: {
type: DataTypes.STRING,
},
customer_id: {
type: DataTypes.STRING,
defaultValue: false,
},
description: {
type: DataTypes.TEXT('long'),
},
token: {
type: DataTypes.TEXT('long'),
},
currency_id: {
type: DataTypes.STRING,
},
non_billable: {
type: DataTypes.BOOLEAN,
defaultValue: true,
},
paymentConfirmation: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
},
});
return PrepaidExpense;
};

View File

@ -0,0 +1,27 @@
module.exports = (sequelize, DataTypes) => {
const PurchaseForecast = sequelize.define('purchaseForecast', {
dollarClosingBalance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
nairaClosingBalance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
month: {
type: DataTypes.STRING,
},
forecastType: {
type: DataTypes.STRING,
}, // 1 day, 1 week, 1month, 2month etc,
currency: {
type: DataTypes.STRING,
},
userId: {
type: DataTypes.STRING,
allowNull: false,
},
});
return PurchaseForecast;
};

View File

@ -0,0 +1,61 @@
module.exports = (sequelize, DataTypes) => {
const Purchase = sequelize.define('purchase', {
purchaseOrderId: {
type: DataTypes.STRING,
},
vendorId: {
type: DataTypes.STRING,
},
vendorName: {
type: DataTypes.STRING,
},
status: {
type: DataTypes.STRING,
},
purchaseOrderNumber: {
type: DataTypes.STRING,
},
refrenceNumber: {
type: DataTypes.STRING,
},
date: {
type: DataTypes.STRING,
},
deliveryDate: {
type: DataTypes.STRING,
},
currencyCode: {
type: DataTypes.STRING,
},
total: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
naira: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
dollar: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
// Amount after deducting from payment made
balance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
exchangeRate: {
type: DataTypes.DECIMAL(10, 6),
defaultValue: 0.0,
},
forecastType: {
type: DataTypes.STRING,
},
userId: {
type: DataTypes.STRING,
allowNull: false,
},
});
return Purchase;
};

21
src/models/rate.model.js Normal file
View File

@ -0,0 +1,21 @@
module.exports = (sequelize, DataTypes) => {
const Rate = sequelize.define('rate', {
old: {
type: DataTypes.DECIMAL(10, 2),
defaultValue: 0.0,
},
latest: {
type: DataTypes.DECIMAL(10, 2),
defaultValue: 0.0,
},
forecastType: {
type: DataTypes.STRING,
},
userId: {
type: DataTypes.STRING,
allowNull: false,
},
});
return Rate;
};

View File

@ -0,0 +1,27 @@
module.exports = (sequelize, DataTypes) => {
const SaleForecast = sequelize.define('saleForecast', {
dollarClosingBalance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
nairaClosingBalance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
month: {
type: DataTypes.STRING,
},
forecastType: {
type: DataTypes.STRING,
}, // 1 day, 1 week, 1month, 2month etc,
currency: {
type: DataTypes.STRING,
},
userId: {
type: DataTypes.STRING,
allowNull: false,
},
});
return SaleForecast;
};

61
src/models/sale.model.js Normal file
View File

@ -0,0 +1,61 @@
module.exports = (sequelize, DataTypes) => {
const Sale = sequelize.define('sale', {
saleOrderId: {
type: DataTypes.STRING,
},
customerId: {
type: DataTypes.STRING,
},
customerName: {
type: DataTypes.STRING,
},
status: {
type: DataTypes.STRING,
},
salesOrderNumber: {
type: DataTypes.STRING,
},
refrenceNumber: {
type: DataTypes.STRING,
},
date: {
type: DataTypes.STRING,
},
shipmentDate: {
type: DataTypes.STRING,
},
currencyCode: {
type: DataTypes.STRING,
},
total: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
naira: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
dollar: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
// Amount after deducting from payment made
balance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
exchangeRate: {
type: DataTypes.DECIMAL(10, 6),
defaultValue: 0.0,
},
forecastType: {
type: DataTypes.STRING,
},
userId: {
type: DataTypes.STRING,
allowNull: false,
},
});
return Sale;
};

17
src/models/token.model.js Normal file
View File

@ -0,0 +1,17 @@
module.exports = (sequelize, DataTypes) => {
const Token = sequelize.define('token', {
// Refresh token generated after successful authentication with zoho
zohoRefreshToken: {
type: DataTypes.TEXT('long'),
},
zohoTokenExpiry: {
type: DataTypes.DATE,
},
zohoTokenDate: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
});
return Token;
};

51
src/models/user.model.js Normal file
View File

@ -0,0 +1,51 @@
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('user', {
firstName: {
type: DataTypes.STRING,
},
lastName: {
type: DataTypes.STRING,
},
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
},
password: {
type: DataTypes.STRING,
},
// status is true if the user has verified their email address and completed registration
status: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
// role is true:1 for admin and false:0 for user
role: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
// super admin
super: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
// Token generated when an invite is sent to a user
inviteToken: {
type: DataTypes.TEXT('long'),
},
// Access token generated when a user logs in
token: {
type: DataTypes.TEXT('long'),
},
inviteDate: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
isZohoAuthenticated: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
});
return User;
};

View File

@ -0,0 +1,51 @@
module.exports = (sequelize, DataTypes) => {
const VendorPayment = sequelize.define('vendorPayment', {
paymentId: {
type: DataTypes.STRING,
},
vendorId: {
type: DataTypes.STRING,
},
vendorName: {
type: DataTypes.STRING,
},
billNumbers: {
type: DataTypes.STRING,
},
refrenceNumber: {
type: DataTypes.STRING,
},
date: {
type: DataTypes.STRING,
},
currencyCode: {
type: DataTypes.STRING,
},
amount: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
// Amount after deducting from purchase order
balance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
purchaseForcastbalance: {
type: DataTypes.DECIMAL(12, 2),
defaultValue: 0.0,
},
exchangeRate: {
type: DataTypes.DECIMAL(10, 6),
defaultValue: 0.0,
},
forecastType: {
type: DataTypes.STRING,
},
userId: {
type: DataTypes.STRING,
allowNull: false,
},
});
return VendorPayment;
};

View File

@ -0,0 +1,10 @@
module.exports = (sequelize, DataTypes) => {
const ZohoRate = sequelize.define('zohorate', {
rate: {
type: DataTypes.DECIMAL(10, 2),
defaultValue: 0.0,
},
});
return ZohoRate;
};

115
src/routes/auth.routes.js Normal file
View File

@ -0,0 +1,115 @@
const authController = require('../controllers/handlers/auth.handler');
const authSchema = require('../schemas/auth.schema');
const verifyToken = require('../controllers/auth/verifyToken');
const inviteUserOpts = {
schema: authSchema.inviteUserOpts,
handler: authController.inviteUserHandler,
};
const registerUserOpts = {
schema: authSchema.registerUserOpts,
handler: authController.registerUserHandler,
};
const loginUserOpts = {
schema: authSchema.loginUserOpts,
handler: authController.loginUserHandler,
};
const getUserOpts = {
schema: authSchema.getUserOpts,
handler: authController.getUserHandler,
};
const getUsersOpts = {
schema: authSchema.getUsersOpts,
handler: authController.getUsersHandler,
};
const deleteUserOpts = {
handler: authController.deleteUserHandler,
};
const refreshTokenOpts = {
schema: authSchema.refreshTokenOpts,
handler: authController.refreshTokenHandler,
};
const updatePasswordOpts = {
schema: authSchema.updatePasswordOpts,
handler: authController.updatePasswordHandler,
};
const updateRoleOpts = {
//schema: authSchema.updatePasswordOpts,
handler: authController.updateUserRoleHandler,
};
const forgotPasswordOpts = {
schema: authSchema.forgotPasswordOpts,
handler: authController.forgotPasswordHandler,
};
const resetPasswordOpts = {
schema: authSchema.resetPasswordOpts,
handler: authController.resetPasswordHandler,
};
const authRoutes = async (fastify, options) => {
// invite a new user
fastify.post('/invite', inviteUserOpts);
// register a new user
fastify.post('/register', registerUserOpts);
// login a user
fastify.post('/login', loginUserOpts);
// refresh token
fastify.post('/token/refresh', refreshTokenOpts);
// forgot password
fastify.post('/password/forgot', forgotPasswordOpts);
// reset password
fastify.post('/password/reset', resetPasswordOpts);
fastify
.register(require('@fastify/auth'))
.after(() => privateRoutes(fastify));
};
const privateRoutes = async (fastify, options) => {
// view my profile
fastify.get('/me', {
preHandler: fastify.auth([verifyToken]),
...getUserOpts,
});
// view all users
fastify.get('/users', {
preHandler: fastify.auth([verifyToken]),
...getUsersOpts,
});
// update user role
fastify.patch('/users/role/update', {
preHandler: fastify.auth([verifyToken]),
...updateRoleOpts,
});
// update user password
fastify.post('/password/update', {
preHandler: fastify.auth([verifyToken]),
...updatePasswordOpts,
});
// delete user role
fastify.delete('/users/delete/:id', {
preHandler: fastify.auth([verifyToken]),
...deleteUserOpts,
});
};
module.exports = authRoutes;

0
src/routes/nobills Normal file
View File

246
src/routes/zoho.routes.js Normal file
View File

@ -0,0 +1,246 @@
const tokenController = require("../controllers/handlers/token.handler");
const zohoController = require("../controllers/handlers/zoho.handler");
const forecastController = require("../controllers/handlers/forecast.handler");
const exchangeController = require("../controllers/handlers/exchange.handler");
const verifyToken = require("../controllers/auth/verifyToken");
const nonbillschema = require('../schemas/nonbill.schema');
const inventorySchema = require('../schemas/inventory.schema');
const authController = require('../controllers/handlers/auth.handler');
const getZohoInventoryOpts = {
schema: inventorySchema.getZohoInventoryOpts,
handler: exchangeController.getZohoInventoryHandler,
};
const generateZohoTokenOpts = {
handler: tokenController.generateZohoTokenHandler,
};
const refreshZohoTokenOpts = {
handler: tokenController.refreshZohoTokenHandler,
};
const getNonbillableExpenseOpts = {
schema: nonbillschema.nonBillablesOpts,
handler: exchangeController.getNonbillableExpense,
};
const getAccrued = {
schema: nonbillschema.accruedExpenseOpts,
handler: authController.saveAccruedPaymentHandler,
};
const updateAccrued = {
schema: nonbillschema.accruedExpenseOpts,
handler: authController.updateAccruedPaymentHandler,
};
const getPrepaid = {
schema: nonbillschema.prepaidExpenseOpt,
handler: authController.savePrepaidPaymentHandler,
};
const updatePrepaid = {
schema: nonbillschema.prepaidExpenseOpt,
handler: authController.savePrepaidPaymentHandler,
};
const exchangeRateOpts = {
handler: exchangeController.getExchangeRateHandler,
};
const exchangeRatesOpts = {
handler: exchangeController.getAllExchangeRateHandler,
};
const downloadExchangeRateOpts = {
handler: exchangeController.downloadExchangeRate,
};
const updateExchangeRateOpts = {
handler: exchangeController.updateExchangeRateHandler,
};
const generateReportOpts = {
handler: zohoController.generateReportHandler,
};
const salesOrderOpts = {
handler: zohoController.salesOrderHandler,
};
const resyncForecastApplicationOpts = {
handler: forecastController.resyncHandler,
};
const fetchBankAccountsOpts = {
handler: forecastController.bankAccountsHandler,
};
const createOpeningbalanceOpts = {
handler: forecastController.createOpeningBalanceHandler,
};
const getAccruedPaymentOpts = {
handler: forecastController.ACcruedExpHandler,
};
const getPrepaidPaymentOpts = {
handler: forecastController.PrepaidExpHandler,
};
const createOverdraftOpts = {
handler: forecastController.createOverdraftHandler,
};
const updateOverdraftOpts = {
handler: forecastController.updateOverdraftHandler,
};
const getOverdraftsOpts = {
handler: forecastController.getOverdraftHandler,
};
const getChartDataOpts = {
handler: forecastController.getChartDataHandler,
};
const deleteOverdraftOpts = {
handler: forecastController.deleteOverdraftHandler,
};
const downloadOpeningBalanceOpts = {
handler: forecastController.downloadOpeningBalance,
}
const zohoRoutes = async (fastify, options) => {
fastify.get("/zoho/opening/balance/create", createOpeningbalanceOpts);
fastify
.register(require("@fastify/auth"))
.after(() => privateRoutes(fastify));
};
const privateRoutes = async (fastify, options) => {
// add overdraft
fastify.post("/zoho/overdraft", {
preHandler: fastify.auth([verifyToken]),
...createOverdraftOpts,
});
//post inventory
fastify.post("/zoho/getinventory", {
preHandler: fastify.auth([verifyToken]),
...getZohoInventoryOpts,
});
// update overdraft
fastify.put("/zoho/overdraft/:id", {
preHandler: fastify.auth([verifyToken]),
...updateOverdraftOpts,
});
//download opening balance
fastify.post("/zoho/bank/opening-balance/download", {
preHandler: fastify.auth([verifyToken]),
...downloadOpeningBalanceOpts,
})
// delete overdraft
fastify.delete("/zoho/overdraft/:id", {
preHandler: fastify.auth([verifyToken]),
...deleteOverdraftOpts,
});
fastify.get("/zoho/overdraft", {
preHandler: fastify.auth([verifyToken]),
...getOverdraftsOpts,
});
fastify.post("/zoho/chart", {
preHandler: fastify.auth([verifyToken]),
...getChartDataOpts,
});
fastify.get("/zoho/bank/accounts", {
preHandler: fastify.auth([verifyToken]),
...fetchBankAccountsOpts,
});
fastify.get("/zoho/bank/getallprepaidexp", {
preHandler: fastify.auth([verifyToken]),
...getPrepaidPaymentOpts,
});
fastify.get("/zoho/bank/getallaccrued", {
preHandler: fastify.auth([verifyToken]),
...getAccruedPaymentOpts,
});
fastify.post("/zoho/exchange/rate/download", {
preHandler: fastify.auth([verifyToken]),
...downloadExchangeRateOpts,
});
fastify.post("/zoho/getNonbillableExpenses", {
preHandler: fastify.auth([verifyToken]),
...getNonbillableExpenseOpts,
});
fastify.post("/zoho/postPrepaid", {
//preHandler: fastify.auth([verifyToken]),
...getPrepaid,
});
fastify.put("/zoho/updatePrepaid", {
//preHandler: fastify.auth([verifyToken]),
...updatePrepaid,
});
fastify.post("/zoho/postAccrued", {
//preHandler: fastify.auth([verifyToken]),
...getAccrued,
});
fastify.put("/zoho/updateAccrued", {
//preHandler: fastify.auth([verifyToken]),
...updateAccrued,
});
// get exchange rate from zoho
fastify.get("/zoho/exchange/rate", {
preHandler: fastify.auth([verifyToken]),
...exchangeRatesOpts,
});
fastify.get("/zoho/exchange/rate/:number/:period", {
preHandler: fastify.auth([verifyToken]),
...exchangeRateOpts,
});
fastify.put("/zoho/exchange/rate/:id", {
preHandler: fastify.auth([verifyToken]),
...updateExchangeRateOpts,
});
// generate report from zoho
fastify.post("/zoho/generate/report", {
preHandler: fastify.auth([verifyToken]),
...generateReportOpts,
});
// get list of sales order
fastify.post("/zoho/sales/order", {
preHandler: fastify.auth([verifyToken]),
...salesOrderOpts,
});
// generate a zoho token
fastify.post("/zoho/token/generate", {
preHandler: fastify.auth([verifyToken]),
...generateZohoTokenOpts,
});
// refresh a zoho token
fastify.get("/zoho/token/refresh", {
preHandler: fastify.auth([verifyToken]),
...refreshZohoTokenOpts,
});
// resync application
fastify.delete("/zoho/forecast/resync", {
preHandler: fastify.auth([verifyToken]),
...resyncForecastApplicationOpts,
});
};
module.exports = zohoRoutes;

278
src/schemas/auth.schema.js Normal file
View File

@ -0,0 +1,278 @@
const status = require('../helpers/status');
const inviteUserOpts = {
tags: ['Authentication'],
description: 'Invite a new user to join the application',
body: {
required: ['email'],
type: 'object',
properties: {
email: {
type: 'string',
format: 'email',
description: 'The email of the user',
},
},
},
response: {
200: status.success,
204: status.noConent,
400: status.badRequest,
500: status.internalServer,
},
logLevel: 'debug',
};
const registerUserOpts = {
tags: ['Authentication'],
description: 'Register a new user to join the application',
body: {
required: ['firstName', 'lastName', 'inviteToken', 'email', 'password'],
type: 'object',
properties: {
firstName: {
type: 'string',
description: 'The first name of the user',
},
lastName: {
type: 'string',
description: 'The last name of the user',
},
inviteToken: {
type: 'string',
description: 'The token generated when an invite is sent to a user',
},
email: {
type: 'string',
format: 'email',
description: 'The email of the user',
},
password: {
type: 'string',
description: 'The password of the user',
},
},
},
response: {
200: status.success,
204: status.noConent,
400: status.badRequest,
500: status.internalServer,
},
logLevel: 'debug',
};
const loginUserOpts = {
tags: ['Authentication'],
description: 'Login a user to the application',
body: {
required: ['email', 'password'],
type: 'object',
properties: {
email: {
type: 'string',
format: 'email',
description: 'The email of the user',
},
password: {
type: 'string',
description: 'The password of the user',
},
},
},
response: {
200: status.success,
204: status.noConent,
400: status.badRequest,
500: status.internalServer,
},
logLevel: 'debug',
};
const refreshTokenOpts = {
tags: ['Authentication'],
description: 'A user who wants to refresh token',
body: {
required: ['refreshToken'],
type: 'object',
properties: {
refreshToken: {
type: 'string',
description: 'Token',
},
},
},
response: {
200: status.success,
204: status.noConent,
400: status.badRequest,
500: status.internalServer,
},
logLevel: 'debug',
};
const getUserOpts = {
tags: ['Authentication'],
description: 'Get the user information',
response: {
200: {
type: 'object',
properties: {
data: {
type: 'object',
properties: {
id: { type: 'integer' },
firstName: { type: 'string' },
lastName: { type: 'string' },
email: { type: 'string' },
status: { type: 'boolean' },
role: { type: 'boolean' },
super: { type: 'boolean' },
inviteToken: { type: 'string' },
token: { type: 'string' },
inviteDate: { type: 'string' },
isZohoAuthenticated: { type: 'boolean' },
},
},
},
},
204: status.noConent,
400: status.badRequest,
500: status.internalServer,
},
};
const getUsersOpts = {
tags: ['Authentication'],
description: 'Get all user information',
response: {
200: {
type: 'object',
properties: {
data: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'integer' },
firstName: { type: 'string' },
lastName: { type: 'string' },
email: { type: 'string' },
status: { type: 'boolean' },
role: { type: 'boolean' },
super: { type: 'boolean' },
inviteToken: { type: 'string' },
token: { type: 'string' },
inviteDate: { type: 'string' },
isZohoAuthenticated: { type: 'boolean' },
},
},
},
},
},
204: status.noConent,
400: status.badRequest,
500: status.internalServer,
},
};
const updatePasswordOpts = {
tags: ['Authentication'],
description: 'User who wants to change password',
body: {
required: ['currentPassword', 'newPassword'],
type: 'object',
properties: {
currentPassword: {
type: 'string',
description: 'Current Password',
},
newPassword: {
type: 'string',
description: 'New Password',
},
},
},
response: {
200: status.success,
204: status.noConent,
400: status.badRequest,
500: status.internalServer,
},
security: [
{
apiKey: [],
},
],
logLevel: 'debug',
};
const forgotPasswordOpts = {
tags: ['Authentication'],
description: 'User who forgot password',
body: {
required: ['email'],
type: 'object',
properties: {
eamil: {
type: 'string',
description: 'Email address',
},
},
},
response: {
200: status.success,
204: status.noConent,
400: status.badRequest,
500: status.internalServer,
},
security: [
{
apiKey: [],
},
],
logLevel: 'debug',
};
const resetPasswordOpts = {
tags: ['Authentication'],
description: 'User who wants to reset password',
body: {
required: ['password', 'token'],
type: 'object',
properties: {
password: {
type: 'string',
description: 'Email address',
},
token: {
type: 'string',
description: 'Token',
},
},
},
response: {
200: status.success,
204: status.noConent,
400: status.badRequest,
500: status.internalServer,
},
security: [
{
apiKey: [],
},
],
logLevel: 'debug',
};
module.exports = {
inviteUserOpts,
registerUserOpts,
loginUserOpts,
refreshTokenOpts,
getUserOpts,
getUsersOpts,
updatePasswordOpts,
forgotPasswordOpts,
resetPasswordOpts,
};

View File

@ -0,0 +1,28 @@
const status = require('../helpers/status');
const getZohoInventoryOpts = {
tags: ['Authentication'],
description: 'Add access token',
body: {
required: ['accessToken'],
type: 'object',
properties: {
accessToken: {
type: 'string',
description: 'the accessToken',
},
},
},
response: {
200: status.success,
204: status.noConent,
400: status.badRequest,
500: status.internalServer,
},
logLevel: 'debug',
};
module.exports = {
getZohoInventoryOpts ,
}

View File

@ -0,0 +1,147 @@
const status = require('../helpers/status');
const nonBillablesOpts = {
tags: ['Authentication'],
description: 'Get Nonbillables',
body: {
required: [ 'accessToken'],
type: 'object',
properties: {
accessToken: {
type: 'string',
description: 'The access token is required',
},
},
},
response: {
200: status.success,
204: status.noConent,
400: status.badRequest,
500: status.internalServer,
},
logLevel: 'debug',
};
const prepaidExpenseOpt = {
tags: ['Authentication'],
description: 'Get prepaid expenses',
body: {
required: ['account_id', 'tax_id', 'payment_date', 'amount','reference_number','customer_id','description','currency_id','currency'],
type: 'object',
properties: {
id: {
type: 'integer',
description: 'The id',
},
account_id: {
type: 'string',
description: 'The account id',
},
tax_id: {
type: 'string',
description: 'The first name of the user',
},
payment_date: {
type: 'string',
description: 'The payment date',
},
amount: {
type: 'string',
description: 'The Amount ',
},
reference_number: {
type: 'string',
description: 'The reference number of the user',
},
customer_id: {
type: 'string',
description: 'The password of the user',
},
description: {
type: 'string',
description: 'The password of the user',
},
currency_id: {
type: 'string',
description: 'The password of the user',
},
currency: {
type: 'string',
description: 'The password of the user',
},
paymentConfirmation: {
type: 'integer',
description: 'The payment confirmation flag',
},
},
},
response: {
200: status.success,
204: status.noConent,
400: status.badRequest,
500: status.internalServer,
},
logLevel: 'debug',
};
const accruedExpenseOpts = {
tags: ['Authentication'],
description: 'Get prepaid expenses',
body: {
required: ['account_id', 'tax_id', 'payment_date', 'amount','reference_number','customer_id','description','currency_id','currency'],
type: 'object',
properties: {
id: {
type: 'integer',
description: 'The id',
},
account_id: {
type: 'string',
description: 'The account id',
},
tax_id: {
type: 'string',
description: 'The first name of the user',
},
payment_date: {
type: 'string',
description: 'The payment date',
},
amount: {
type: 'string',
description: 'The Amount ',
},
reference_number: {
type: 'string',
description: 'The reference number of the user',
},
customer_id: {
type: 'string',
description: 'The customerid',
},
description: {
type: 'string',
description: 'The item description',
},
currency_id: {
type: 'string',
description: 'The password of the user',
},
currency: {
type: 'string',
description: 'The currency ',
},
paymentConfirmation: {
type: 'integer',
description: 'The payment confirmation flag',
},
},
},
response: {
200: status.success,
204: status.noConent,
400: status.badRequest,
500: status.internalServer,
},
logLevel: 'debug',
};
module.exports = {
nonBillablesOpts,accruedExpenseOpts,prepaidExpenseOpt,
};

View File

@ -0,0 +1,59 @@
const status = require('../helpers/status');
const addOverdraftOpts = {
tags: ['Overdraft'],
description: 'Add an overdraft account balance',
body: {
required: [
'accountId',
'accountName',
'accountType',
'accountNumber',
'bankName',
'currency',
'amount',
],
type: 'object',
properties: {
accountId: {
type: 'string',
description: 'Bank account id',
},
accountName: {
type: 'string',
description: 'Bank account name',
},
accountType: {
type: 'string',
description: 'Bank account type',
},
accountNumber: {
type: 'string',
description: 'The account number',
},
bankName: {
type: 'string',
description: 'Bank name',
},
currency: {
type: 'string',
description: 'The currency of the bank account',
},
amount: {
type: 'string',
description: 'Overdraft/loan amount for this bank account',
},
},
},
response: {
200: status.success,
204: status.noConent,
400: status.badRequest,
500: status.internalServer,
},
logLevel: 'debug',
};
module.exports = {
addOverdraftOpts,
};