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