asked on
const { ObjectId } = require('mongodb');
const express = require('express');
const router = express.Router();
const twig = require('twig');
const moment = require('moment-timezone');
const fs = require('fs');
const { Util } = require('node-utils');
const Company = require('../services/company.js');
const CompanyContact = require('../services/companycontact.js');
const CompanyAddress = require('../services/companyaddress.js');
const Proposal = require('../services/proposal.js');
const Activity = require('../services/activity.js');
const Prospect = require('../services/prospect.js');
const ServiceModel = require('../services/servicemodel');
const AccountManagement = require('../services/account-management');
const GeoLocations = require('../services/GeoLocations');
// set the default variables we'll be passing to the template engine for ease
// of use.
let base = {
selected: '/companies',
error: false,
msg: '',
data: {}
};
router.post('/companies/ignore', async (req, res) => {
if (typeof req.body.companies == 'undefined' || req.body.companies.length === 0) {
return res.send(Prospect.error('Please provide one or more company ID\'s.'));
}
try {
let deleted = await Company.ignoreTargetSelected(req.body.companies);
res.send(Prospect.success(deleted));
} catch (e) {
res.send(Prospect.error('An unexpected error was encountered.'));
}
});
router.post('/companies/target', async (req, res) => {
if ((typeof req.body.companies == 'undefined' || req.body.companies.length === 0) && (typeof req.body.user == 'undefined')) {
return res.send(Prospect.error('Please provide one or more company ID\'s.'));
}
try {
let _target = await Company.targetSelected(req.body.companies, req.body.user);
res.send(Prospect.success());
} catch (e) {
res.send(Prospect.error('An unexpected error was encountered.'));
}
});
// get the full list/index view
router.get('/companies', async (req, res) => {
let vars = Object.assign({}, base);
//I made a little change here. vars.service_models, in my opinion needs to be called once and only after it's been conclusively determined that there's data in place to be retrieved. So I established "brucesters" as a var to hold the results of the "getList()" function.
// If it's empty, then you seed the appropriate collection. If not, you simply move forward to the "vars.service_models = await ServiceModel.getList) function and all is well. What you see commented out below is the original code
vars.service_models = await ServiceModel.getList();
//Seed data
if (vars.service_models.length == 0) {
let defaultServiceModels = [{ key: 'perFte', name: 'Per FTE' }, { key: 'oneTime', name: 'One Time' }, { key: 'perPerson', name: 'Per Person' }, { key: 'perMonth', name: 'Per Month' }, { key: 'perQuarter', name: 'Per Quarter' }, { key: 'semiAnnually', name: 'Semiannually' }, { key: 'perYear', name: 'Per Year' }];
defaultServiceModels.forEach(async (item) => {
let _id = await ServiceModel.save('new', item);
});
service_models = await ServiceModel.getList();
}
//console.log(vars.service_models);
vars.pipelineColumns = await Company.getPipelineColumns();
let resolveDatesArray = await Company.resolveDatesArray();
res.render('companies.html.twig', vars);
});
router.post('/companies/datatable', async (req, res) => {
let resp = await Company.datatable(req.body, { pipeline: { $ne: '' } });
//console.log(resp);
res.send(resp);
});
router.post('/companies/heatmap', async (req, res) => {
try {
let resp = await Company.heatmap(req.body.ids);
res.send(Company.success(resp));
} catch (err) {
res.status(500).send(Company.error(err));
}
});
router.post('/companies/heatmap/click/draw', async (req, res) => {
try {
let resp = await GeoLocations.heatmapdraw(req.body, 'companies');
res.send(Company.success(resp));
} catch (err) {
console.log(err);
res.status(500).send(Company.error(err));
}
});
router.get('/companies/map/:id?', async (req, res) => {
let vars = Object.assign({}, base);
vars.service_models = await ServiceModel.getList();
vars.pipelineColumns = await Company.getPipelineColumns();
if (typeof req.params.id != 'undefined' || req.params.id != '') {
vars._id = req.params.id;
}
res.render('companies-map.html.twig', vars);
});
router.post('/companies/map/:id?', async (req, res) => {
try {
var pipeline_filters = {};
pipeline_filters.$or = [{ active: { $exists: false } }, { active: true }];
if (typeof req.params.id != 'undefined') {
req.body.filters = typeof req.body.filters == 'undefined' ? [] : req.body.filters;
req.body.filters.push({ "field": "_id", "op": "$eq", "val": Company._getId(req.params.id) });
}
let resp = await Company.datatable(req.body, pipeline_filters);
res.send(resp);
} catch (err) {
console.log('Datatables Error: ', err);
res.status(500).send(Company.error(err));
}
});
router.get('/companies/select2', async (req, res) => {
try {
let data = await Company.select2(req.query.q.term);
res.send(data);
} catch (err) {
res.status(500).send({});
}
});
router.get('/companies/saved-filters', async (req, res) => {
try {
var data = await Company.getSavedFilters();
res.send(data);
} catch (err) {
console.log('Error: ', err);
res.status(500).send(Company.error());
}
});
router.post('/companies/save-filters', async (req, res) => {
try {
let id = await Company.saveFilter(req.body.id, {
type: 'companies',
name: req.body.name,
filters: req.body.filters,
account: global._user.account._id,
active: true,
owner: global._user._id
});
res.send(Company.success({
id: id
}));
} catch (err) {
console.log('Error: ', err);
res.send(Company.error('An unexpected error was encountered. Please try again.'));
}
});
router.post('/companies/delete-saved-filter', async (req, res) => {
try {
await Company.deleteSavedFilter(req.body.id);
res.send(Company.success());
} catch (err) {
res.status(500).send(Company.error());
}
});
// bulk assign companies to a single user - must come before the edit route
router.post('/companies/assign', async (req, res) => {
let vars = Object.assign({}, base);
if (typeof req.body.user == 'undefined' || typeof req.body.companies == 'undefined') {
return res.send(Company.error('Invalid or missing parameters.'));
} else if (typeof req.body.companies == 'string') {
req.body.companies = [req.body.companies];
}
try {
let resp = await Company.assign(req.body.companies, req.body.user);
} catch (err) {
console.log('Error: ', err);
return res.send(Company.error('Invalid parameters provided.'));
}
res.send(vars);
});
router.post('/companies/unassign', (req, res) => {
if (req.body.companies == 'undefined') {
return res.send(Company.error('Invalid or missing parameters.'));
} else if (typeof req.body.companies == 'string') {
req.body.companies = [req.body.companies];
}
try {
let success = Company.unassign(req.body.companies);
res.send(Company.success());
} catch (err) {
res.send(Company.error('An unexpected error was encountered while trying to unassign the user associated with the provided companies. Please try again.'));
}
});
router.post('/companies/add-activity', async (req, res) => {
// make sure the request has a type
if (typeof req.body.type == 'undefined' || req.body.type === '') {
return res.send({ error: true, msg: 'Invalid or missing service/object type. Please refresh the page and try again.' });
} else if (typeof req.body.activityType == 'undefined' || req.body.activityType === '') {
req.body.activityType = req.body.type;
//return res.send({ error: true, msg: 'Invalid or missing service/object type. Please refresh the page and try again.' });
} else if (typeof req.body.company == 'undefined' || req.body.company === '') {
return res.send({ error: true, msg: 'Invalid or missing company ID. Please refresh the page and try again.' });
}
// set the activity type as the main type - I added this in after a request to
// combine several types
req.body.type = req.body.activityType;
// validate the content based on the type
try {
if (Array.isArray(req.body.company)) {
let ids = [];
//var companies = new Set(req.body.company);
var companies = req.body.company.filter(function (elem, pos) { return req.body.company.indexOf(elem) == pos; });// Remove duplicates
var counter = companies.length;
for (var i = 0; i < companies.length; i++) {
try {
counter -= 1;
let _companyId = companies[i];
if (_companyId == 'bulk') {
ids.push("_0");
if (counter === 0) {
let topId = ids[0];
res.send(Company.success({ id: companies[0] }));
}
} else {
let id = await Company.addActivity(_companyId, req.body.type, req.body);
//let data = { id: id, company: bdata.company };
ids.push(id);
if (counter === 0) {
let topId = ids[0];
res.send(Company.success({ id: companies[0] }));
}
}
} catch (e) {
console.log(e);
}
}
} else {
let id = await Company.addActivity(req.body.company, req.body.type, req.body);
res.send(Company.success({ id }));
}
} catch (err) {
res.send(Company.error(err));
}
});
//update activity
router.post('/companies/update-activity', async (req, res) => {
try {
let _data = {
notes : req.body.notes,
date : req.body.date,
company : req.body.company,
type : req.body.type
}
Activity.model.updateOne({ _id: req.body.activityId }, {
$set: {
// set up the data you want to persist to the DB
notes: _data.notes,
"meta.$.date": _data.date,
type: _data.type,
}
},
{ runValidators: true }, (err) => {
if (err) {
console.log(err)
return res.send(Activity.error('Hmmm. Something went wrong!'))
}
res.send(Company.success( { message : "Updated!" } ))
});
} catch (err) {
res.send(Company.error(err))
}
});
// add an address or contact to a company record
router.post('/companies/:orgId/:type(contacts|addresses|brokers)/:id', async (req, res) => {
// make sure the request has a type
if (typeof req.params.type == 'undefined' || req.params.type === '') {
return res.send({ error: true, msg: 'Invalid or missing service/object type. Please refresh the page and try again.' });
} else if (typeof req.params.orgId == 'undefined' || req.params.orgId === '') {
return res.send({ error: true, msg: 'Invalid or missing company ID. Please refresh the page and try again.' });
}
let resp;
// enforce some scoping
req.body.account = global._user.account._id;
req.body.company = Company._getId(req.params.orgId);
// validate the content based on the type
try {
let oldRec = {}, newRec = {}, meta = {};
switch (req.params.type) {
case 'contacts':
if (req.params.id != 'new') { oldRec = await Company.getOneContact(req.params.id, req.params.orgId); }
resp = await Company.saveContact(req.params.id, req.body);
//newRec = await resp;
meta = Company.jsonDiff(oldRec, resp);
Company.addHistory((req.params.id == 'new' ? 'Contact added' : 'Contact updated'), req.params.orgId, '', global._user._id, meta);
break;
case 'addresses':
if (req.params.id != 'new') { oldRec = await Company.getOneAddress(req.params.id, req.params.orgId); }
resp = await Company.saveAddress(req.params.id, req.body);
meta = Company.jsonDiff(oldRec, resp);
Company.addHistory((req.params.id == 'new' ? 'Address added' : 'Address updated'), req.params.orgId, '', global._user._id, meta);
break;
case 'brokers':
if (req.params.id != 'new') { oldRec = await Company.getOneBroker(req.params.id, req.params.orgId); }
resp = await Company.saveBroker(req.params.id, req.body);
delete req.body.company;
delete req.body.account;
newRec = req.body;//await Company.getOneBroker(req.params.id, req.params.orgId);
meta = Company.jsonDiff(oldRec, newRec);
Company.addHistory((req.params.id == 'new' ? 'Broker info added' : 'Broker info updated'), req.params.orgId, '', global._user._id, meta);
break;
default:
return res.send(Company.error('Invalid type provided.'));
break;
}
} catch (err) {
console.log('Save Contacts/Addresses/Brokers Error: ', err);
// if the user is having issues with an invalid address from google, we need to let them know
let msg = (typeof err == 'string' && err.indexOf('Google') > -1) ? err : 'An unexpected error was encountered.';
return res.send(Company.error(msg));
}
res.send(resp);
});
router.get('/companies/:orgId/:type(contacts|addresses|brokers)/:id', async (req, res) => {
try {
let data = null;
switch (req.params.type) {
case 'contacts':
data = await Company.getOneContact(req.params.id, req.params.orgId);//await CompanyContact.getOne(req.params.id);
break;
case 'addresses':
data = await Company.getOneAddress(req.params.id, req.params.orgId);//await CompanyAddress.getOne(req.params.id);
break;
case 'brokers':
data = await Company.getOneBroker(req.params.id, req.params.orgId);
break;
default:
return res.status(500).send(Company.error('Invalid type provided.'));
break;
}
//res.send(Company.success(data));
} catch (err) {
console.log('Get Contact/Address/Brokers Error: ', err);
res.status(500).send(Company.error());
}
});
router.get('/companies/:orgId/:type(contacts|addresses)/delete/:id', async (req, res) => {
console.log("post");
try {
switch (req.params.type) {
case 'contacts':
await CompanyContact.deleteItem(req.params.id);
break;
case 'addresses':
await CompanyAddress.deleteItem(req.params.id);
break;
default:
return res.status(500).send(Company.error('Invalid type provided.'));
break;
}
res.send(Company.success());
} catch (err) {
console.log('Delete Contact/Address Error: ', err);
res.status(500).send(Company.error());
}
});
router.post('/companies/export', async (req, res) => {
try {
let rows = await Company.exportToCSV(req.body);
res.send(Company.success(rows));
} catch (err) {
console.log('Export Error: ', err);
res.status(500).send(Company.error());
}
});
router.get('/companies/proposals/:proposalId/:route', async (req, res) => {
if (typeof req.params.proposalId == 'undefined') {
return res.send(Company.error('It looks like your request was improperly defined. Please try again.'));
}
try {
var paths = [
{ path: 'owner', model: 'User' },
{ path: 'company', model: 'Company' },
{ path: 'company.addresses', model: 'CompanyAddress' },
{ path: 'company.contacts', model: 'CompanyContact' },
{ path: 'services.service', model: 'Service' }
];
if (typeof req.params.route != 'undefined') {
if (req.params.route == 'market') {
paths[1] = { path: 'prospect', model: 'prospect' };
//paths.push({ path: 'prospect', model: 'prospect' });
let proposal = await Proposal.getOne(req.params.proposalId, paths);
if (paths[1].path == 'prospect') {
if (proposal) {
var prospect_proposal = {
_id: proposal._id,
name: proposal.name,
pipelineIndex: proposal.pipelineIndex,
_pipelineDate: proposal._pipelineDate,
pipeline: proposal.pipeline,
company: await Prospect.getOne(proposal.company),
owner: proposal.owner,
dates: proposal.dates,
_range: proposal._range,
files: proposal.files,
services: proposal.services
};
}
return res.send(Proposal.success(prospect_proposal));
}
}
}
let proposal = await Proposal.getOne(req.params.proposalId);
res.send(Proposal.success(proposal));
} catch (err) {
res.send(Proposal.error('An unexpected error was encountered while trying to load your proposal. Please try again.' + '\nDetails:' + JSON.stringify(err)));
}
});
router.get('/companies/proposal/pdf/:proposalId/', async (req, res) => {
if (typeof req.params.proposalId == 'undefined') {
return res.send(Company.error('It looks like your request was improperly defined. Please try again.'));
}
try {
let proposal = await Proposal.getOne(req.params.proposalId);
proposal.account = await AccountManagement.getOne(proposal.account);
proposal.value = 0;
proposal.services.forEach((service, index) => {
let price = service.amount || service.price;
if (service.model === 'perFte' || service.model === 'oneTime' || service.model ==='perPerson') {
proposal.services[index].total = parseFloat(price) * service.numOfEmp;
} else {
proposal.services[index].total = parseFloat(price) * service.numOfEmp * service.duration;
}
proposal.value = proposal.value + proposal.services[index].total;
});
proposal.company.addresses = proposal.company.addresses.length > 0 ? proposal.company.addresses[proposal.company.addresses.length - 1] : proposal.company.addresses;
proposal.company.contacts = proposal.company.contacts.length > 0 ? proposal.company.contacts[proposal.company.contacts.length - 1] : proposal.company.contacts;
res.render('proposal-template-pdf.html.twig', proposal, (error, html) => {
if (error) {
console.log(error);
res.send(Proposal.error('An unexpected error was encountered while trying to load your proposal. Please try again.' + '\nDetails:' + JSON.stringify(error)));
}
res.send(Proposal.success({ pdf: html, logo: proposal.account.logo, address: proposal.account.address }));
});
} catch (err) {
res.send(Proposal.error('An unexpected error was encountered while trying to load your proposal. Please try again.' + '\nDetails:' + JSON.stringify(error)));
}
});
router.post('/companies/proposals/save/:proposalId', async (req, res) => {
// make sure the proposal object was provided
if (typeof req.body.proposal == 'undefined') {
return res.send(Company.error('It looks like your request was improperly defined. Please try again.'));
} else if (typeof req.body.proposal.services == 'undefined') {
// make sure the services object is provided an not empty
return res.send(Company.error('Please add at least one service to the proposal.'));
}
// properly format the services since I did it the lazy way - mitch
let services = [];
for (let i in req.body.proposal.services.service) {
let service = {
service: req.body.proposal.services.service[i],
model: req.body.proposal.services.model[i],
serviceDates: req.body.proposal.services.serviceDates[i],
duration: req.body.proposal.services.duration[i],
numOfEmp: req.body.proposal.services.numOfEmp[i],
description: req.body.proposal.services.description[i]
};
service.price = (typeof req.body.proposal.services.price != 'undefined') ? req.body.proposal.services.price[i] : req.body.proposal.services.amount[i];
//if (typeof req.body.proposal.services.pipelineIndex != 'undefined') { service.pipelineIndex = req.body.proposal.services.pipelineIndex[i];}
services.push(service);
}
req.body.proposal.services = services;
// format the file uploads - for the db schema we don't need the files to be
// indexed by their key.
req.body.proposal.files = Company.array_values(req.body.proposal.files);
// in a couple of required fields
req.body.proposal.owner = req.body.proposal.user;//global._user._id;
req.body.proposal.active = true;
// try to save it now
try {
//req.body.proposal._pipelineDate = Date.now;
if (req.params.proposalId == 'new') {
req.body.proposal._range = [new Date(moment().toISOString()), new Date(moment().toISOString())];
req.body.proposal.pipelineDuration = [{
pipelineFrom: req.body.proposal.pipeline, fDate: new Date(moment().toISOString()),
pipelineTo: req.body.proposal.pipeline, tDate: new Date(moment().toISOString())
}];
} else {
req.body.proposal._range = (typeof req.body.proposal._range == 'undefined') ? [] : (Array.isArray(req.body.proposal._range) ? req.body.proposal._range : [req.body.proposal._range]);
req.body.proposal._range[0] = new Date(moment().toISOString());
req.body.proposal._range[1] = (typeof req.body.proposal._range[1] == 'undefined') ? new Date(moment().toISOString()) : req.body.proposal._range[1];
let exProposal = await Proposal.getOne(req.params.proposalId);
if (exProposal) {
//exProposal = exProposal.toObject();
exProposal.pipelineDuration = typeof exProposal.pipelineDuration == 'undefined' ? [] : exProposal.pipelineDuration;
exProposal.pipelineDuration.push({
pipelineFrom: exProposal.pipeline, fDate: exProposal._pipelineDate,
pipelineTo: req.body.proposal.pipeline, tDate: new Date(moment().toISOString())
});
req.body.proposal.pipelineDuration = exProposal.pipelineDuration;
}
}
//update['$set']['_range.0'] = new Date(moment().toISOString());
if (Array.isArray(req.body.proposal.company)) {
let ids = [];
var companies = req.body.proposal.company.filter(function (elem, pos) { return req.body.proposal.company.indexOf(elem) == pos; });// Remove duplicates;
var counter = companies.length;
for (var i = 0; i < companies.length; i++) {
try {
counter -= 1;
var bdata = req.body.proposal;
bdata.company = companies[i];
let id = await Proposal.save(req.params.proposalId, bdata);
let data = { id: id, company: bdata.company };
ids.push(data);
//Include ProposalId in Prospect
Prospect.includeTargetId(id, bdata.company);
if (counter === 0) {
res.send(Proposal.success(ids[0]));
}
} catch (e) {
console.log(e);
}
}
} else {
let oldProposal = req.params.proposalId == 'new' ? {} : await Proposal.getOneForHistory(req.params.proposalId);
let id = await Proposal.save(req.params.proposalId, req.body.proposal);
if (id) {
let newProposal = await Proposal.getOneForHistory(id);
let meta = Proposal.jsonDiff(oldProposal, newProposal);
await Proposal.addHistory((req.params.proposalId == 'new' ? 'Proposal created' : 'Proposal updated'), req.body.proposal.company, id, global._user._id, meta);
let clientMessage = req.body['proposal-company-clientMessage'];
if (typeof clientMessage != 'undefined' || clientMessage.length > 0) {
let oldMessage = await Company.getOne(req.body.proposal.company);
await Company.addClientMessage(req.body.proposal.company, clientMessage);
if (oldMessage.clientMessage != clientMessage) {
let meta = await Proposal.jsonDiff({ 'Message': oldMessage.clientMessage }, { 'Message': clientMessage });
await Proposal.addHistory('Company updated', req.body.proposal.company, '', global._user._id, meta);
}
}
}
let data = { id: id, company: req.body.proposal.company };
res.send(Proposal.success(data));
}
} catch (err) {
res.send(Proposal.error('An unexpected error was encountered while trying to save your proposal. Please try again.' + '\nDetails:' + JSON.stringify(err)));
}
});
router.post('/companies/proposals/:proposalId/delete-file/:key', async (req, res) => {
try {
// load the proposal record
let proposal = await Proposal.getOne(req.params.proposalId);
// make sure the proposal even has files
if (typeof proposal.files == 'undefined') {
return res.send(Proposal.success());
}
let filterfiles = [];
let fileUrl = '';
proposal.files.forEach(function (file) {
if (file.key == req.params.key) {
fileUrl = file.url;
} else {
filterfiles.push(file);
}
});
// update the proposal
Proposal.model.updateOne({ _id: proposal._id }, { $set: { files: filterfiles } }, { runValidators: true }, (err) => {
if (err) {
return res.send(Proposal.error('An unexpected error was encountered while trying to delete the file. Please try again.'));
}
// delete the file
fs.unlinkSync(`public${fileUrl}`);
// and wrap it up
res.send(Proposal.success());
});
} catch (err) {
console.log('Error: ', err);
res.status(500).send();
}
});
router.post('/companies/save-file/:companyId', async (req, res) => {
try {
// convert the file date to an object from string
req.body.file.date = new Date(moment(req.body.file.date, 'MM/DD/YYYY hh:mm A'));
// save the file to the company record
Company.model.updateOne(Company._enforceScope({
_id: ObjectId(req.params.companyId)
}), {
$push: {
files: req.body.file
}
}, {
runValidators: true
}, (err) => {
if (err) {
res.status(500).send();
} else {
let meta = Company.jsonDiff({}, { files: '<a href="' + req.body.file.url + '" target="_blank">' + req.body.file.name + '</a>' });
Company.addHistory('File attached', req.params.companyId, '', global._user._id, meta);
res.send({ success: true });
}
});
} catch (err) {
res.status(500).send();
}
});
router.post('/companies/delete-file/:companyId/:key', async (req, res) => {
try {
// load the proposal record
let company = await Company.getOne(req.params.companyId);
// make sure the company even has files
if (typeof company.files == 'undefined') {
return res.send(Company.success());
}
let filterfiles = [];
let fileUrl = '';
company.files.forEach(function (file) {
if (file.key == req.params.key) {
fileUrl = file.url;
} else {
filterfiles.push(file);
}
});
Company.model.updateOne({ _id: company._id }, { $set: { files: filterfiles } }, { runValidators: true }, (err) => {
if (err) {
return res.send(Company.error('An unexpected error was encountered while trying to delete the file. Please try again.'));
}
// delete the file
fs.unlinkSync(`public${fileUrl}`);
// and wrap it up
res.send(Company.success());
});
} catch (err) {
console.log('Error: ', err);
res.status(500).send();
}
});
// get the details of a single company
router.get('/companies/:orgId', async (req, res) => {
let vars = Object.assign({
userSettings: global._user.account.settings
}, base);
try {
//let company = await Prospect.getOne(req.params.orgId);
//vars.data = Prospect.success(company);
let company = await Company.getOne(req.params.orgId);
if (company) {
let meta = company.meta;
if (meta) { meta.fundingStatus = (typeof meta.fundingType != 'undefined') ? meta.fundingType : meta.fundingStatus; }
}
// get the company proposals
company.proposals = typeof company.proposals == 'undefined' ? {} : company.proposals;
company.proposals = await Company.getProposals(company._id);
if (company.proposals) {
if (company.proposals.length > 0) {
company.proposals.forEach((row, rsi) => {
if (typeof row.services != 'undefined') {
row.services.forEach(async (item, ri) => {
if (item.service) {
company.proposals[rsi].services[ri].service = await Company.getService(item.service);
}
});
}
});
}
}
// get the company activity
company.activity = typeof company.activity == 'undefined' ? [] : company.activity;
company.activity = await Company.getActivity(company._id);
// make sure there is a default file key
company.files = typeof company.files == 'undefined' ? {} : company.files;
company.files = company.files || {};
// load the proposal files
company.proposalFiles = typeof company.proposalFiles == 'undefined' ? [] : company.proposalFiles;
company.proposalFiles = await Company.getFiles(company._id);
company.route = 'companies';
// check if the request just wants a JSON payload - Used for Proposal form
if (req.query.json) {
return res.send(Company.success(company));
}
vars.data = company;
} catch (err) {
vars.msg = `<div class="alert alert-danger">${err.message}</div>`;
}
res.render('companies-edit.html.twig', vars);
});
router.get('/companies/getActivity/:activityId', async(req, res) => {
company.activity = await Company.getActivity(company._id);
});
// save a company record
router.post('/companies/save/:companyId', async (req, res) => {
try {
// save the record normally
let oldObj = {}, newObj = {};
if (req.params.companyId != 'new') { oldObj = await Company.getOneForHistory(req.params.companyId); }
if (req.params.companyId === 'new') {
// Newly added companies should be saved and then appear on the Companies and Market Analysis pages
req.body.source = "Companies";
req.body.clientImport = global._user.account._id; // Source Account ID
let marketId = await Prospect.save(req.params.companyId, req.body);
req.body.market = marketId;
}
let id = await Company.save(req.params.companyId, req.body);
newObj = await Company.getOneForHistory(id);
let meta = Company.jsonDiff(oldObj, newObj);
let history = await Company.addHistory((req.params.companyId == 'new' ? 'Company created' : 'Company updated'), id, '', global._user._id, meta);
res.send(Company.success({ id }));
} catch (err) {
console.log('Error: ', err);
res.send(Company.error('An unexpected error was encountered while trying to save your company.'));
}
});
router.get('/companies/:companyId/get/custom/fields', async (req, res) => {
try {
if (req.params.companyId === undefined) { return res.send(Company.error('Undefined parameter company ID')); }
let customFields = await Company.getOne(req.params.companyId);
res.send(Company.success(customFields));
} catch (err) {
console.log('Error: ', err);
res.send(Company.error('An unexpected error was encountered while trying to save your company.'));
}
});
router.post('/companies/:companyId/save/custom/fields', async (req, res) => {
try {
if (req.params.companyId === undefined) { return res.send(Company.error('Undefined parameter company ID')); }
let body = {};
Object.keys(req.body).forEach((key) => {
body[req.body[key][0]] = req.body[key][1];
});
Object.keys(body).forEach((key) => {
if (key === null || key === '') {
delete body[key];
}
});
let beforeCF = await Company.getOne(req.params.companyId);
let customFields = await Company.save(req.params.companyId, { customFields: body });
let afterCF = await Company.getOne(req.params.companyId);
let diff = await Company.jsonDiff(beforeCF.customFields, afterCF.customFields);
let history = await Company.addHistory('Custom Fields Updated', req.params.companyId, '', global._user._id, diff);
res.send(Company.success(customFields));
} catch (err) {
console.log('Error: ', err);
res.send(Company.error('An unexpected error was encountered while trying to save your company.'));
}
});
router.post('/companies/delete', async (req, res) => {
if (typeof req.body.companies == 'undefined' || req.body.companies.length === 0) {
return res.send(Company.error('Please provide one or more company ID\'s.'));
}
try {
await Company.softDeleteItems(req.body.companies);
res.send(Company.success());
} catch (err) {
res.send(Company.error('An unexpected error was encountered.'));
}
});
router.post('/companies/proposal/delete/:proposalId', async (req, res) => {
if (typeof req.params.proposalId == 'undefined') {
return res.send(Company.error('It looks like your request was improperly defined. Please try again.'));
}
try {
await Proposal.softDeleteItems([req.params.proposalId]);
res.send(Company.success());
} catch (err) {
res.send(Proposal.error('An unexpected error was encountered while trying to load your proposal. Please try again.'));
}
});
router.get('/companies/activities/:activityId', async(req, res)=> {
try {
//console.log(req.params.activityId);
let activityId=req.params.activityId;
let activity = await Activity.getOne(activityId);
res.send(activity);
} catch (err) {
res.send(error('An unexpected error was encountered while trying to load your proposal. Please try again.'));
}
});
module.exports = router;
const { ObjectId } = require('mongodb');
const { Util } = require('node-utils');
const moment = require('moment-timezone');
const mongoose = require('mongoose');
const uuid = require('uuid/v4');
const Service = require('./service');
const CompanySchema = require('../schemas/CompanySchema');
const ActivitySchema = require('../schemas/ActivitySchema');
const EventSchema = require('../schemas/EventSchema');
const ProposalSchema = require('../schemas/ProposalSchema');
const SavedFilterSchema = require('../schemas/SavedFilterSchema');
const UserSchema = require('../schemas/UserSchema');
const CompanyContactSchema = require('../schemas/CompanyContactSchema');
const CompanyAddressSchema = require('../schemas/CompanyAddressSchema');
const CompanyAddress = require('../services/companyaddress');
const GeoLocations = require('../services/GeoLocations');
const objectModels = require('../lib/object-models');
const Vendors = require('../lib/Vendors');
const ServiceSchema = require('../schemas/ServiceSchema');
const ProductsAndService = mongoose.model('Service', ServiceSchema);
const Proposal = mongoose.model('Proposal', ProposalSchema);
const User = require('../services/user.js');
let userModel = mongoose.model('User', UserSchema);
class Company extends Service {
constructor() {
super();
this.collection = 'companies';
this.plural = 'companies';
this.singular = 'company';
this.model = mongoose.model('Company', CompanySchema);
this.fields = require('../lib/model-company');
this.fields._id = {};
this.fields._id.type = ObjectId;
this.corePipeline = [
{
$lookup: {
from: 'users',
foreignField: '_id',
localField: 'owner',
as: 'owner'
}
},
{$unwind: {path: '$owner',preserveNullAndEmptyArrays: true}},
//{ "$addFields": { "brokers": { "$slice": ["$brokers", 1] } } },
//{ "$unwind": { path: "$brokers", preserveNullAndEmptyArrays: true } },
//{ "$addFields": { "contacts": { "$slice": ["$contacts", 1] } } },
//{ "$unwind": { path: "$contacts", preserveNullAndEmptyArrays: true } },
//{ '$match': { 'addresses.postal': { '$in': global._user.account.postalCodes } } }
];
}
datatable(input, baseFilters = {}) {
//console.log(global._user.account.postalCodes);
return new Promise((resolve, reject) => {
let config = this._getDataTablesFilters(input);
let pipeline = [];
let countPipeline = []; // the filters used for counts
// add in the base filters
config.filters = Util.merge(baseFilters, config.filters);
// set the base pipeline where/match clause
pipeline.push({ $match: this._enforceScope() });
// add sorting
if (Object.keys(config.sort).length > 0) {
if (typeof config.sort[0] == 'undefined') {
pipeline.push({ $sort: config.sort });
}
}
// Owner
pipeline.push(
{$lookup: {from: 'users',let: { 'ownerId': '$owner' },pipeline: [{$match: {$expr: {$and: [{ $eq: ["$_id", "$$ownerId"] }]}}},{$project: {name: {$concat: ['$firstName', ' ', '$lastName']}}}],as: 'owner'}},
{ $unwind: { path: '$owner', preserveNullAndEmptyArrays: true } }
);
// Broker
pipeline.push(
{ "$addFields": { "brokers": { "$slice": ["$brokers", 1] } } },
{ "$unwind": { path: "$brokers", preserveNullAndEmptyArrays: true } }
);
pipeline.push(
{ "$addFields": { "contacts": { "$slice": ["$contacts", 1] } } },
{ "$unwind": { path: "$contacts", preserveNullAndEmptyArrays: true } }
);
//pipeline.push(
// { "$addFields": { "addresses": { "$slice": ["$addresses", 1] } } },
// { "$unwind": { path: "$addresses", preserveNullAndEmptyArrays: true } }
//);
if(global._user.account.postalCodes) {
pipeline.push({
$match: {
$or: [
{ 'addresses.postal': { $in: global._user.account.postalCodes } },
{ clientImport: global._user.account._id }
]
}
});
}
// Search text
if (typeof input.search != 'undefined') {
if (input.search.value) {
var searchQuery = this._getDataTablesSearch(input.search.value);
if (typeof searchQuery != 'undefined') {
pipeline.push({ $match: searchQuery });
}
}
}
// set the filters pipeline where/match clause
if (Object.keys(config.filters).length > 0) {
pipeline.push({ $match: config.filters });
}
// set the count pipeline here since all of the filters should be applied
// by this point
countPipeline = Array.from(pipeline);
// add the limits
pipeline.push({ $skip: config.limits[0] });
pipeline.push({ $limit: config.limits[1] });
// add in a datatable specific field
pipeline.push({
$addFields: {
DT_RowId: '$_id',
currentUserId: global._user._id //for 'Assign to me' link
}
});
// call the lifecycle method to allow extending classes to customize just
// the pipeline without having to overwrite the whole method
pipeline = this._beforeDatatablesPipeline(pipeline);
this.model.aggregate(pipeline).then(async (data) => {
resolve({
draw: parseInt(input.draw),
data: data,
recordsFiltered: await this.__aggCount(countPipeline),//filter wrong? await this._count(this._enforceScope()),
recordsTotal: await this._count(this._enforceScope())
});
}).catch((err) => {
//console.log("it's here we've got a problem");
console.log('Aggregate Error: ', err);
reject(err);
});
});
}
getOne(companyId) {
return new Promise((resolve, reject) => {
companyId = this._getId(companyId);
if (!companyId) {
return reject('Invalid company ID provided.');
}
// create a clone of the pipeline so we don't alter the original
let pipeline = this.corePipeline.slice(0);
// add an initial match stage to the core pipeline
pipeline.unshift({
$match: this._enforceScope({
_id: companyId
})
});
this.model.aggregate(pipeline).then((data) => {
if (data.length === 0) {
reject('Could not find record.');
} else {
resolve(data[0]);
}
}).catch((err) => {
reject(err);
});
});
}
getOneContact(contactId, companyId) {
return new Promise((resolve, reject) => {
let company_Id = this._getId(companyId);
let contact_id = this._getId(contactId);
let pipeline = [{ $match: { _id: company_Id } },
{$project: {
contacts: {$filter: {
input: "$contacts",
as: "contacts",
cond: { $eq: ["$$contacts._id", contact_id] }
}}
}},
{$addFields:{'contacts.company':'$_id'}},
{$unwind:{path:"$contacts",preserveNullAndEmptyArrays:true}},
{ $project: { 'contacts': 1, _id: 0 } }];
this.model.aggregate(pipeline).then((rows) => {
resolve(rows[0].contacts);
}).catch((error) => {
console.log(error.message);
reject(error.message);
});
});
}
saveContact(contactId, contact) {
return new Promise((resolve, reject) => {
let companyId = contact.company;
contact.active = true;
if (contactId === 'new') {
delete contact.company;
this.model.updateOne({ _id: companyId }, { $push: { contacts: contact } }, { setDefaultsOnInsert: true }).then(response => {
contact.company = companyId;
resolve(contact);
}).catch(err => {
reject(err);
});
} else {
this.model.updateOne({ _id: companyId },
{
$set: {
'contacts.$[i].email': contact.email,
'contacts.$[i].name': contact.name,
'contacts.$[i].phone': contact.phone
}
},
{
arrayFilters: [{
"i._id": this._getId(contactId)
}], setDefaultsOnInsert: true
}
).then(response => {
contact.company = companyId;
resolve(contact);
}).catch(err => {
reject(err);
});
}
});
}
getOneAddress(addressId, companyId) {
return new Promise((resolve, reject) => {
let company_Id = this._getId(companyId);
let address_id = this._getId(addressId);
let pipeline = [{ $match: { _id: company_Id } },
{$project: {
addresses: {$filter: {
input: "$addresses",
as: "addresses",
cond: { $eq: ["$$addresses._id", address_id] }
}}
}},
{$addFields:{'addresses.company':'$_id'}},
{$unwind:{path:"$addresses",preserveNullAndEmptyArrays:true}},
{ $project: { 'addresses': 1, _id: 0 } }];
this.model.aggregate(pipeline).then((rows) => {
resolve(rows[0].addresses);
}).catch((error) => {
console.log(error.message);
reject(error.message);
});
});
}
saveAddress(addressId, address) {
return new Promise((resolve, reject) => {
let companyId = address.company;
address.active = true;
if (addressId === 'new') {
delete address.company;
this.model.updateOne({ _id: companyId }, { $push: { addresses: address } }, { setDefaultsOnInsert: true }).then(response => {
address.company = companyId;
resolve(address);
}).catch(err => {
reject(err);
});
} else {
this.model.updateOne({ _id: companyId },
{
$set: {
'addresses.$[i].street': address.street,
'addresses.$[i].city': address.city,
'addresses.$[i].state': address.state,
'addresses.$[i].postal': address.postal,
//'addresses.$[i].geo': geoaddress.geo,
'addresses.$[i].hash': address.hash,
'addresses.$[i].nonGeo': address.nonGeo
}
},
{
arrayFilters: [{
"i._id": this._getId(addressId)
}], setDefaultsOnInsert: true
}
).then(response => {
address.company = companyId;
resolve(address);
}).catch(err => {
reject(err);
});
}
});
}
getOneBroker(index, companyId) {
return new Promise((resolve, reject) => {
let company_Id = this._getId(companyId);
this.model.findOne({ _id: company_Id, account: global._user.account._id }, (err, res) => {
if (err) { console.log(err); reject(err); }
resolve(res.brokers[index]);
});
});
}
saveBroker(index, broker) {
return new Promise((resolve, reject) => {
let companyId = this._getId(broker.company);
let accountId = this._getId(broker.account);
delete broker.company;
delete broker.account;
this.model.findOne({ _id: companyId, account: accountId }, (err, res) => {
if (err) { console.log(err); reject(err); }
res.brokers = res.brokers ? res.brokers : [];
if (index == 'new') {
res.brokers.push(broker);
broker = res.brokers;
} else {
res.brokers[index] = broker;
broker = res.brokers;
}
this.model.updateOne({ _id: companyId, account: accountId }, { $set: { brokers: broker }}, (err, raw) => {
if (err) { console.log(err); reject(err); }
resolve(raw);
});
});
});
}
//Fix: This function will check for dates field type array and convert it into object. Converts array field to Object
resolveDatesArray() {
return new Promise((resolve, reject) => {
let condition = { dates: { $type: "array" } };
this.model.updateMany(condition, { dates: {} }, { upsert: true }, (err, raw) => {
if (err) {
console.log(err);
reject(err);
}
resolve(raw);
});
});
}
select2(query) {
return new Promise((resolve, reject) => {
this.model.aggregate([
{
'$match': this._enforceScope({
name: {
'$regex': query,
'$options': 'i'
}
})
},
{
'$sort': {
name: 1
}
},
{ '$limit': 25 },
{
'$project': {
id: '$_id',
text: '$name'
}
}
]).then((rows) => {
resolve(rows);
}).catch((err) => {
reject(err);
});
});
}
assign(companies, user) {
return new Promise((resolve, reject) => {
let where = this._enforceScope({ _id: { $in: [] } });
let update = { $set: {} };
if (typeof user == 'undefined' || companies == 'undefined') {
return reject('Invalid or missing parameters.');
}
let ids = [];
for (let i in companies) {
where._id['$in'].push(this._getId(companies[i]));
ids.push(this._getId(companies[i]));
}
let pipeline = [{ $match: { _id: { $in: ids } } }, { $lookup: { from: "users", localField: "owner", foreignField: "_id", as: "owner" } }];
this.model.aggregate(pipeline).then((beforeRows) => {
update = {
$set: {
owner: this._getId(user),
account: global._user.account._id,//new added
active: true,//new added
'dates.assigned': new Date(moment().toISOString())// new added for reporting, pipeline target column display
}
};
this.model.updateMany(where, update, (err, res) => {
if (err) {
reject(this.error('Invalid parameters provided.'));
} else {
let count = 0;
beforeRows.forEach((oldDoc) => {
pipeline = [{ $match: { _id: oldDoc._id } }, { $lookup: { from: "users", localField: "owner", foreignField: "_id", as: "owner" } }, { $limit: 1 }];
this.model.aggregate(pipeline).then(async (rows) => {
let newDoc = rows[0];
let meta = await this.jsonDiff(
{ owner: (oldDoc.owner.length > 0) ? '<a href="javascript:void(0);" class="user-info" data-user-id="' + oldDoc.owner[0]._id + '">' + oldDoc.owner[0].firstName + ' ' + oldDoc.owner[0].lastName + '</a>' : 'Unassigned' },
{ owner: (newDoc.owner.length > 0) ? '<a href="javascript:void(0);" class="user-info" data-user-id="' + newDoc.owner[0]._id + '">' + newDoc.owner[0].firstName + ' ' + newDoc.owner[0].lastName + '</a>' : 'Unassigned' }
);
let history = await this.addHistory('Owner change', newDoc._id, '', global._user._id, meta);
count = count + 1;
if (count = beforeRows.length) {
resolve(this.success());
}
});
});
}
});
});
});
}
unassign(companies) {
return new Promise((resolve, reject) => {
let update = { $set: { owner: null } };
let id;
if (companies == 'undefined') {
reject('Invalid or missing parameters.');
}
for (let i in companies) {
id = this._getId(companies[i]);
if (id) {
let pipeline = [
{ $match: { _id: id } },
{ $lookup: { from: "users", localField: "owner", foreignField: "_id", as: "owner" } },
{ $limit: 1 }];
this.model.aggregate(pipeline).then((res) => {
if (res.length > 0) {
this.model.updateOne({ _id: id }, update, async(err, doc) => {
if (err) { console.log(err); }
if (typeof res[0].owner != 'undefined') {
let meta = await this.jsonDiff(
{ owner: res[0].owner.length > 0 ? '<a href="javascript:void(0)" class="user-info" data-route="company" data-user-id="' + res[0].owner[0]._id + '">' + res[0].owner[0].firstName + " " + res[0].owner[0].lastName + '</a>' : "Unassigned" },
{ owner: 'Unassinged' }
);
await this.addHistory('Owner change', id, '', global._user._id, meta);
}
if ((parseInt(i)+1) === companies.length) {
resolve(this.success());
}
});
}
}).catch((error) => { console.log(error); });
}
}
});
}
softDeleteItems(companies) {
return new Promise((resolve, reject) => {
if (typeof companies == 'undefined' || companies === 0) {
return reject(Company.error('Please provide one or more company ID\'s.'));
}
for (let i in companies) {
let id = this._getId(companies[i]);
if (id) {
let pipeline = [
{ $match: { _id: id } },
{ $lookup: { from: "proposals", localField: "_id", foreignField: "company", as: "proposals" } },
{ $limit: 1 }];
this.model.aggregate(pipeline).then((res) => {
if (res.length > 0) {
this.model.updateOne({ _id: id }, {$set: {deleted: true,active: false}}, async (err, doc) => {
if (err) { console.log(err); }
// delete Proposals
if (res[0].proposals.length > 0) {
await Proposal.updateMany({ company: id }, { $set: { active: false } }, async (err, doc) => {
if (err) { console.log(err);}
// Todo nothing.
});
}
let meta = await this.jsonDiff(
{ active: true },
{ active: false, deleted:true }
);
await this.addHistory('Company deleted', id, '', global._user._id, meta);
if ((parseInt(i) + 1) === companies.length) {
resolve(this.success());
}
});
}
}).catch((error) => { console.log(error); });
}
}
});
}
getActivity(companyId) {
return new Promise((resolve, reject) => {
const Activity = mongoose.model('Activity', ActivitySchema);
//let pipeline = [
// {
// $match: {
// account: global._user.account._id,
// company: companyId
// }
// },
// {
// $sort: {
// date: -1
// }
// },
// { $limit: 100 },
// {
// $lookup: {
// from: 'users',
// foreignField: '_id',
// localField: 'user',
// as: 'user'
// }
// },
// { $unwind: '$user' }
// ];
// Activity.aggregate(pipeline).then((rows) => {
// var activity = [];
// for (let i in rows) {
// // get the icon by type
// rows[i].icon = this.getIconByType(rows[i].type);
// // add it to the list
// activity.push(rows[i]);
// }
// resolve(activity);
//}).catch((err) => {
// reject(err);
//});
let pipeline = [
{
$match: {
account: global._user.account._id,
company: companyId
}
},
{
$lookup: {
from: 'users',
foreignField: '_id',
localField: 'user',
as: 'user'
}
},
{ $unwind: '$user' },
{
$group: {
'_id': {
year: { $year: "$date" },
month: { $month: "$date" },
day: { $dayOfMonth: "$date" }
},
activities: {
$push: {
_id: '$_id',
type: '$type',
company: '$company',
proposal: '$proposal',
user: { _id: '$user._id', 'firstName': '$user.firstName', 'lastName': '$user.lastName' },
date: '$date',
meta: '$meta'
}
}
}
},
{ $project: { date: '$_id', activities: 1, _id: 0 } },
{ $sort: { "activities.date": -1 } }
];
Activity.aggregate(pipeline).then((rows) => {
var activity = [];
for (let i in rows) {
for (let j in rows[i].activities) {
let today = moment(rows[i].activities[j].date).isSame(moment(), 'day'); // get true/false indicating today
rows[i].activities[j].icon = this.getIconByType(rows[i].activities[j].type);
rows[i].activities[j].prettyDate = today ? "today" : moment(rows[i].activities[j].date).fromNow(); // set prettyDate to 'today' if we have a match, otherwise use moment
rows[i].activities[j].readableDate = moment(rows[i].activities[j].date).format('MMM Do - LT');
rows[i].activities[j].doableDate = moment(rows[i].activities[j].date).format('m/d/Y');
}
/*
=======================================================
the above for loop was put in as a replacement for what you see below in order to facilitate "today" rather than 5-6 hours ago
or whatever might be registered in hours rather than something more readable like "today" (Bruce Gust 4/13/2020)
=======================================================
*/
/*
for (let j in rows[i].activities) {
rows[i].activities[j].icon = this.getIconByType(rows[i].activities[j].type);
rows[i].activities[j].prettyDate = moment(rows[i].activities[j].date).fromNow();
rows[i].activities[j].readableDate = moment(rows[i].activities[j].date).format('MMM Do - LT');
rows[i].activities[j].doableDate = moment(rows[i].activities[j].date).format('m/d/Y');
}
*/
// add it to the list
rows[i].prettyDate = moment.utc(rows[i].date.year + '-' + rows[i].date.month + '-' + rows[i].date.day).local().fromNow();
activity.push(rows[i]);
}
//console.log(activity.activities);
resolve(activity);
}).catch((err) => {
reject(err);
});
});
}
getFiles(companyId) {
return new Promise((resolve, reject) => {
const Proposal = mongoose.model('Proposal', ProposalSchema);
Proposal.aggregate([
{
$match: {
company: companyId,
files: {
$exists: true
}
}
},
{ $unwind: '$files' },
{
$project: {
proposal: '$_id',
name: '$name',
file: '$files'
}
}
]).then((rows) => {
resolve(rows);
}).catch((err) => {
console.log('Error: ', err);
reject(err);
});
});
}
addActivity(companyId, type, data) {
return new Promise(async(resolve, reject) => {
if (typeof type != 'string' || ['call', 'visit', 'reminder', 'event'].indexOf(type.trim()) == -1) {
return reject('Invalid type provided');
}
const Schema = mongoose.Schema;
const Base = mongoose.model('Activity', ActivitySchema);
// to track activity on each pipeline stage
if (data.proposal != 'undefined') {
if (data.proposal != 'bulk') {
if (data.proposal) {
await Proposal.findById(this._getId(data.proposal), (err, res) => {
if (err) { console.log(err); }
if (res) {
data.pipeline = res.pipeline;
}
});
}
}
}
if (type == 'event') {
// define dynamic Schema
const Event = Base.discriminator('event', new mongoose.Schema({meta: {start: Date,end: Date,subject: String,participants: String,location: String,details: String}}));
let _data = {
company: new ObjectId(companyId),
type: type.trim(),
meta: {
start: data.start,
end: data.end,
subject: data.subject,
participants: data.participants,
location: data.location,
details: data.details
},
user: global._user._id,
account: global._user.account._id
};
if (data.proposal != 'undefined') {
if (data.proposal != 'bulk') {
if (data.proposal) {
_data.proposal = data.proposal;
_data.pipeline = data.pipeline;
}
}
}
Event.create(_data, (err, record) => {
if (err) {
console.log('Error: ', err);
reject('An unexpected error was encountered. Please try again.');
} else {
resolve(record._id);
}
});
} else {
// define dynamic Schema
//FIX: descriminator with name 'call' already exists.
const ActivityModel = Base.discriminators ? Object.keys(Base.discriminators).indexOf(type.trim()) >= 0 ? Base.discriminators[type.trim()] : Base.discriminator(type.trim(), new mongoose.Schema({ meta: { date: Date, notes: String } })) : Base.discriminator(type.trim(), new mongoose.Schema({ meta: { date: Date, notes: String } }));
//const ActivityModel = Base.discriminator(type.trim(), new mongoose.Schema({ meta: { date: Date, notes: String } }));
let _data = {
company: new ObjectId(companyId),
type: type.trim(),
meta: {
date: data.date,
notes: data.notes
},
user: global._user._id,
account: global._user.account._id
};
if (data.proposal != 'undefined') {
if (data.proposal != 'bulk') {
if (data.proposal) {
_data.proposal = data.proposal;
_data.pipeline = data.pipeline;
}
}
}
ActivityModel.create(_data, (err, record) => {
if (err) {
console.log('Error: ', err);
reject('An unexpected error was encountered. Please try again.');
} else {
resolve(record._id);
}
});
}
});
}
retrieveActivity(activityId) {
}
deleteActivity(activityId) {
return new Promise((resolve, reject) => {
let match = this._enforceScope({
_id: new ObjectId(activityId)
});
const Activity = mongoose.model('Activity', ActivitySchema);
Activity.deleteOne(match, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
exportToCSV(input) {
return new Promise((resolve, reject) => {
let config = this._getDataTablesFilters(input);
let pipeline = [];
let baseFilters = {
account: global._user.account._id,
active: true,
deleted: {
$exists: false
}
};
// add in the base filters
config.filters = Util.merge(baseFilters, config.filters);
// set the base pipeline where/match clause
pipeline.push({ $match: this._enforceScope() });
// lifecylce method to give the user the ability to inject setup stages to
// the pipeline before we start adding in user-defined filters, limits and
// sorting options
pipeline = this._startPipeline(pipeline);
// Search text
if (typeof input.search != 'undefined') {
if (input.search.value) {
var searchQuery = this._getDataTablesSearch(input.search.value);
if (typeof searchQuery != 'undefined') {
pipeline.push({ $match: searchQuery });
}
}
}
// set the filters pipeline where/match clause
if (Object.keys(config.filters).length > 0) {
pipeline.push({ $match: config.filters });
}
// add sorting
if (Object.keys(config.sort).length > 0) {
pipeline.push({ $sort: config.sort });
}
// we need to format the arrays - not sure what the heck is going on with
// using $addresses.0.street in the projection stage below, but I had some
// issues getting it to work
pipeline.push({
$addFields: {
_contact: {
$slice: ['$contacts', 1]
},
_address: {
$slice: ['$addresses', 1]
}
}
});
pipeline.push({
$unwind: {
path: '$owner',
preserveNullAndEmptyArrays: true
}
});
pipeline.push({
$unwind: {
path: '$_contact',
preserveNullAndEmptyArrays: true
}
});
pipeline.push({
$unwind: {
path: '$_address',
preserveNullAndEmptyArrays: true
}
});
// add the project stage
pipeline.push({
$project: {
name: '$name',
owner: '$owner.name',
contact_name: '$_contact.name',
contact_email: '$_contact.email',
contact_phone: '$_contact.phone',
address_street: '$_address.street',
address_city: '$_address.city',
address_state: '$_address.state',
address_postal: '$_address.postal',
address_county: '$_address.county',
industry: '$meta.industry',
ftes: '$meta.ftes',
fteGrouping: '$meta.fteGrouping',
premium: '$meta.premium',
premiumPerFte: '$meta.premiumPerFte',
brokerCommissionTotal: '$meta.brokerCommissionTotal',
brokerFeesTotal: '$mata.brokerFeesTotal',
fundingType: '$meta.fundingType',
ein: '$meta.ein'
}
});
// get the data
this.model.aggregate(pipeline).then((rows) => {
const papaparse = require('papaparse');
console.log('Data: ', JSON.stringify(rows, undefined, 2));
let csv = papaparse.unparse(rows);
resolve(csv);
}).catch((err) => {
reject(err);
});
});
}
_startPipeline(pipeline) {
let coreClone = this.corePipeline.slice(0);
// remove the first stage of the core to add in the below projection
coreClone.shift();
// add in the owner's info
coreClone.unshift({
$lookup: {
from: 'users',
let: { 'ownerId': '$owner' },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ["$_id", "$$ownerId"] }
]
}
}
},
{
$project: {
name: {
$concat: ['$firstName', ' ', '$lastName']
}
}
}
],
as: 'owner'
}
});
return pipeline.concat(coreClone);
}
targetSelected(companies = [], user) {
return new Promise(async (resolve, reject) => {
try {
if (companies.length > 0 && user.length > 0) {
let Targetpipelines = await this.getPipelineColumns();
var index = Targetpipelines.map(function (o) { return o.key; }).indexOf("target");
let Targetpipeline = Targetpipelines[index];
if (typeof Targetpipeline != 'undefined') {
let count = 0;
companies.forEach((company) => {
let targetProposal = {
name: '_target', owner: this._getId(user), company: this._getId(company), pipeline: Targetpipeline._id, active: true, services: [], files: [],
//dates: { target: new Date(moment().toISOString())},
_range: [new Date(moment().toISOString()), new Date(moment().toISOString())],
_pipelineDate: new Date(),
pipelineIndex: 0,
account: global._user.account._id,
pipelineDuration: [{
pipelineFrom: Targetpipeline._id, fDate: new Date(moment().toISOString()),
pipelineTo: Targetpipeline._id, tDate: new Date(moment().toISOString())
}]
};
targetProposal.dates = typeof targetProposal.dates == 'undefined' ? {} : targetProposal.dates;
targetProposal.dates[Targetpipeline._id] = new Date(moment().toISOString()), new Date(moment().toISOString());
Proposal.updateOne({ owner: this._getId(user), company: this._getId(company), name: '_target' }, targetProposal, { runValidators: true, upsert: true }, async(error, res) => {
if (error) {
console.log(error);
}
let targetUser = await this.getUserbyID(user);//await User.getOne(this._getId(user));
if (targetUser) {
let meta = { 'Target Selected to': "<a href='javascript:void(0);' class='user-info' data-user-id='" + targetUser._id + "' data-route='market'>" + targetUser.firstName + " " + targetUser.lastName + "</a>" };
this.addHistory('Target selected', this._getId(company), '', global._user._id, meta);
}
count = count + 1;
if (count == companies.length) {
resolve(true);
}
});
});
} else {
reject('Pipeline Column is undefined.');
}
} else {
reject('Invalid or undefined company id or user id provided.');
}
} catch (e) {
console.log(e);
reject(e);
}
});
}
getUserbyID(id) {
return new Promise(async (resolve, reject) => {
if (id) {
userModel.findById(this._getId(id), (err, res) => {
if (err) { console.log(err); resolve(false);}
resolve(res);
});
} else {
resolve(false);
}
});
}
ignoreTargetSelected(companies = []) {
return new Promise(async (resolve, reject) => {
try {
if (companies.length > 0) {
let Targetpipelines = await this.getPipelineColumns();
var index = Targetpipelines.map(function (o) { return o.key; }).indexOf("target");
let Targetpipeline = Targetpipelines[index];
if (typeof Targetpipeline != 'undefined') {
let companyIds = [];
companies.forEach(async (company) => {
companyIds.push(this._getId(company));
await this.addHistory('Target ignored', this._getId(company), '', global._user._id, {});
});
Proposal.deleteMany({ company: { $in: companyIds }, name: '_target' }, (error) => {
if (error) { reject(error); }
resolve(true);
});
} else {
reject('Pipeline Column is undefined.');
}
} else {
reject('Invalid or undefined company id provided.');
}
} catch (e) {
console.log(e);
reject(e);
}
});
}
getOneForHistory(id) {
return new Promise((resolve, reject) => {
id = this._getId(id);
if (!id) {
return reject('Invalid prospect ID provided.');
}
let pipeline = [{ $match: { _id: id } },
{
$lookup: {
from: "users",
let: { "ownerId": "$owner" },
pipeline: [
{ $match: { $expr: { $eq: ["$_id", "$$ownerId"] } } },
{
$project: {
_id: 0,
assigned: { $concat: ["<a href='javascript:void(0)' class='user-info' data-user-id='", { $toString: "$_id" }, "'>", "$firstName", " ", "$lastName", "</a>"] }
}
}
],
as: "owner"
}
},
{ $unwind: { path: "$owner", preserveNullAndEmptyArrays: true } },
{ $addFields: { owner: "$owner.assigned" } }
];
this.model.aggregate(pipeline).then((data) => {
if (data.length === 0) {
reject('Could not find record.');
} else {
resolve(data[0]);
}
}).catch((err) => {
reject(err);
});
});
}
getProposals(companyId) {
return new Promise((resolve, reject) => {
let scopedFilters = this._enforceScope();
var pipeline = [
{
$match: {
company: this._getId(companyId),
name: { "$ne": "_target" },// should not show _target proposals they are just 'Target Selected' pipeline
$or: [{ "active": { "$exists": false } }, { "active": true }]
}
},
{
$lookup: {
from: "pipelinecolumns",
localField: "pipeline",
foreignField: "_id",
as: "pipeline"
}
},
{ $unwind: "$pipeline" },
{
$lookup: {
from: "services",
localField: "services.service",
foreignField: "_id",
as: "products"
}
},
{
$lookup: {
from: "users",
localField: "owner",
foreignField: "_id",
as: "owner"
}
},
{
$project: {
"products.model": 0, "products.account": 0, "products.amount": 0,
"owner.settings": 0, "owner.password": 0, "owner.email": 0, "owner.mobile": 0, "owner.account": 0
}
},
{
$unwind: "$owner"
}];
Proposal.aggregate(pipeline, (err, rows) => {
if (err) {
reject(err);
} else {
resolve(rows);
}
});
});
}
getService(serviceId) {
return new Promise((resolve, reject) => {
let pipeline = [{ $match: { "_id": serviceId } }];
ProductsAndService.aggregate(pipeline, (err, rows) => {
if (err) {
reject(err);
} else {
resolve(rows[0]);
}
});
});
}
heatmap(ids) {
return new Promise((resolve, reject) => {
let pipeline = [];
let _ids = [];
if (ids && ids.length > 0) {
ids.forEach((id) => {
_ids.push(this._getId(id));
});
let match = { $match: this._enforceScope() };
delete match['$match']['account'];
match['$match']._id = { $in: _ids };
pipeline.push(match);
pipeline.push(
{ $unwind: { path: "$addresses", preserveNullAndEmptyArrays: true } },
{ "$addFields": { "company": { _id: "$_id", name: "$name" } } },
{
"$lookup": {
"from": "geo_locations", "let": { "addressId": "$addresses._id" }, "pipeline": [
{ "$match": { "$and": [{ "$expr": { "$eq": ["$address", "$$addressId"] } }, { "$expr": { "$eq": ["$active", true] } }] } }], "as": "geo_locations"
}
},
{ $unwind: { path: "$geo_locations", preserveNullAndEmptyArrays: true } },
{
"$project": {
"_id": 0,
"type": "Feature",
"properties": {
"name": '$name',
"popupContent": "$name",
"_id": "$_id", "street": "$addresses.street", "city": "$addresses.city", 'state': '$addresses.state', "postal": "$addresses.postal", "country": "$addresses.country", "county": "$addresses.county",
"addresses": "$addresses",
"company": "$company", "nonGeo": "$addresses.nonGeo"
},
"geometry": "$geo_locations.geo"
}
}
);
this.model.aggregate(pipeline).then(async (rows) => {
let count = 0;
rows.forEach(async (row, index) => {
if (row.geometry === undefined) {
row.properties.addresses = row.properties.addresses ? row.properties.addresses : {};
row.properties.addresses.company = row.properties.company._id;
let geoAddress = await this.getGeoAddress(row.properties.addresses);
rows[index].geometry = geoAddress.geo;
count = count + 1;
} else { count = count + 1; }
if (rows.length == count) {
resolve({ "type": "FeatureCollection", "features": rows });
}
});
}).catch((err) => {
console.log('Aggregate Error: ', err);
reject(err);
});
} else {
//reject('undefined parameter ids provided.');
resolve({ "type": "FeatureCollection", "features": [] });
}
});
}
getGeoAddress(address = {}) {
return new Promise(async (resolve, reject) => {
try {
if (typeof address.geo != 'undefined') {
resolve(address);
} else {
var isValidAddress = false;
address.street = address.street ? address.street.trim() : address.street;
if (!address || address.street === '' || address.city === '' || address.state === '' || address.postal === '' || address.street === undefined || address.city === undefined || address.state === undefined || address.postal === undefined) {
isValidAddress = false;
address.error = 'invalid';
resolve(address);
}
else {
isValidAddress = true;
}
if (isValidAddress) {
let geoaddress = await CompanyAddress.getGeocode(address);
if (geoaddress) {
if (geoaddress.error_message) {
address.error = geoaddress.error_message;
resolve(address);
} else {
GeoLocations.model.updateOne(
{ address: address._id },//, account: global._user.account._id
{ $set: { address: address._id, company: address.company, geo: geoaddress.geo } },//account: global._user.account._id,
{ upsert: true, setDefaultsOnInsert: true }, (err) => {
if (err) {
resolve(address);
} else {
this.model.updateOne({ _id: address.company },
{
$set: {
'addresses.$[i].street': geoaddress.street,
'addresses.$[i].city': geoaddress.city,
'addresses.$[i].state': geoaddress.state,
'addresses.$[i].postal': geoaddress.postal,
//'addresses.$[i].geo': geoaddress.geo,
'addresses.$[i].hash': geoaddress.hash,
'addresses.$[i].nonGeo': geoaddress.nonGeo
}
},
{
arrayFilters: [
{
"i._id": address._id
}]
}
).then(response => {
resolve(geoaddress);
}).catch(err => {
address.error = err.msg;
resolve(address);
});
}
});
//CompanyAddress.model.updateOne({ _id: address._id }, { '$set': { street: geoaddress.street, city: geoaddress.city, state: geoaddress.state, postal: geoaddress.postal, geo: geoaddress.geo, hash: geoaddress.hash, nonGeo: geoaddress.nonGeo } }, { runValidators: true }, (err) => {
// if (err) {
// address.error = err.msg;
// resolve(address);
// } else {
// resolve(geoaddress);
// //let ret = { _id: location._id, name: record.name, address: location.address, coordinates: location.address.geo.coordinates };
// //resolve(ret);
// }
//});
}
} else {
address.error = 'invalid';
resolve(address);
}
}
}
} catch (e) {
// return reject(e);
console.log(e);//console.log(e.message);
resolve(address);
}
});
}
}
module.exports = new Company();