initial commit
This commit is contained in:
commit
ce4ff7eac5
26
.env.example
Normal file
26
.env.example
Normal 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
105
.gitignore
vendored
Normal 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
|
36
config.js
Normal file
36
config.js
Normal 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
2858
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
44
package.json
Normal file
44
package.json
Normal 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
60
server.js
Normal 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
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
src/controllers/.DS_Store
vendored
Normal file
BIN
src/controllers/.DS_Store
vendored
Normal file
Binary file not shown.
31
src/controllers/auth/generateToken.js
Normal file
31
src/controllers/auth/generateToken.js
Normal 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 };
|
37
src/controllers/auth/verifyRefreshToken.js
Normal file
37
src/controllers/auth/verifyRefreshToken.js
Normal 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 };
|
25
src/controllers/auth/verifyToken.js
Normal file
25
src/controllers/auth/verifyToken.js
Normal 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
BIN
src/controllers/handlers/.DS_Store
vendored
Normal file
Binary file not shown.
797
src/controllers/handlers/auth.handler.js
Normal file
797
src/controllers/handlers/auth.handler.js
Normal 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,
|
||||
};
|
615
src/controllers/handlers/exchange.handler.js
Normal file
615
src/controllers/handlers/exchange.handler.js
Normal 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
|
||||
};
|
999
src/controllers/handlers/forecast.handler.js
Normal file
999
src/controllers/handlers/forecast.handler.js
Normal 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,
|
||||
};
|
7
src/controllers/handlers/inflow.handler.js
Normal file
7
src/controllers/handlers/inflow.handler.js
Normal file
@ -0,0 +1,7 @@
|
||||
//getInvoice
|
||||
|
||||
// getCustomerPayments
|
||||
|
||||
// getSalesOrder
|
||||
|
||||
//processSales
|
7
src/controllers/handlers/outflow.handler.js
Normal file
7
src/controllers/handlers/outflow.handler.js
Normal file
@ -0,0 +1,7 @@
|
||||
// getBill
|
||||
|
||||
// getVendorPayments
|
||||
|
||||
// getPurchaseOrder
|
||||
|
||||
// processPurchases
|
190
src/controllers/handlers/token.handler.js
Normal file
190
src/controllers/handlers/token.handler.js
Normal 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 };
|
2292
src/controllers/handlers/zoho.handler.js
Normal file
2292
src/controllers/handlers/zoho.handler.js
Normal file
File diff suppressed because it is too large
Load Diff
106
src/emailTemplates/index.html
Normal file
106
src/emailTemplates/index.html
Normal 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
BIN
src/emailTemplates/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
147
src/emailTemplates/password_reset_email_template.html
Normal file
147
src/emailTemplates/password_reset_email_template.html
Normal 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>
|
108
src/emailTemplates/signup_email_template.html
Normal file
108
src/emailTemplates/signup_email_template.html
Normal 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
399
src/helpers/dbQuery.js
Normal 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
77
src/helpers/email.js
Normal 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
46
src/helpers/status.js
Normal 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 };
|
54
src/models/accruedExpenses.forecast.model.js
Normal file
54
src/models/accruedExpenses.forecast.model.js
Normal 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;
|
||||
};
|
||||
|
32
src/models/bank.account.model.js
Normal file
32
src/models/bank.account.model.js
Normal 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;
|
||||
};
|
27
src/models/bill.forecast.model.js
Normal file
27
src/models/bill.forecast.model.js
Normal 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
56
src/models/bill.model.js
Normal 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;
|
||||
};
|
51
src/models/customer.payment.model.js
Normal file
51
src/models/customer.payment.model.js
Normal 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
71
src/models/index.js
Normal 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;
|
29
src/models/initial.balance.model.js
Normal file
29
src/models/initial.balance.model.js
Normal 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;
|
||||
};
|
27
src/models/invoice.forecast.model.js
Normal file
27
src/models/invoice.forecast.model.js
Normal 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;
|
||||
};
|
53
src/models/invoice.model.js
Normal file
53
src/models/invoice.model.js
Normal 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;
|
||||
};
|
14
src/models/opening.balance.model.js
Normal file
14
src/models/opening.balance.model.js
Normal 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;
|
||||
};
|
28
src/models/overdraft.model.js
Normal file
28
src/models/overdraft.model.js
Normal 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;
|
||||
};
|
54
src/models/prepaideExpenses.forecast.model.js
Normal file
54
src/models/prepaideExpenses.forecast.model.js
Normal 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;
|
||||
};
|
27
src/models/purchase.forecast.model.js
Normal file
27
src/models/purchase.forecast.model.js
Normal 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;
|
||||
};
|
61
src/models/purchase.model.js
Normal file
61
src/models/purchase.model.js
Normal 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
21
src/models/rate.model.js
Normal 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;
|
||||
};
|
27
src/models/sale.forecast.model.js
Normal file
27
src/models/sale.forecast.model.js
Normal 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
61
src/models/sale.model.js
Normal 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
17
src/models/token.model.js
Normal 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
51
src/models/user.model.js
Normal 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;
|
||||
};
|
51
src/models/vendor.payment.model.js
Normal file
51
src/models/vendor.payment.model.js
Normal 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;
|
||||
};
|
10
src/models/zoho.rate.model.js
Normal file
10
src/models/zoho.rate.model.js
Normal 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
115
src/routes/auth.routes.js
Normal 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
0
src/routes/nobills
Normal file
246
src/routes/zoho.routes.js
Normal file
246
src/routes/zoho.routes.js
Normal 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
278
src/schemas/auth.schema.js
Normal 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,
|
||||
};
|
28
src/schemas/inventory.schema.js
Normal file
28
src/schemas/inventory.schema.js
Normal 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 ,
|
||||
}
|
147
src/schemas/nonbill.schema.js
Normal file
147
src/schemas/nonbill.schema.js
Normal 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,
|
||||
};
|
59
src/schemas/overdraft.schema.js
Normal file
59
src/schemas/overdraft.schema.js
Normal 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,
|
||||
};
|
Loading…
Reference in New Issue
Block a user