mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
WIP Multi-tenant architecture.
This commit is contained in:
@@ -2,12 +2,10 @@ import express from 'express';
|
||||
import { check, validationResult, oneOf } from 'express-validator';
|
||||
import { difference } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import asyncMiddleware from '../middleware/asyncMiddleware';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import jwtAuth from '@/http/middleware/jwtAuth';
|
||||
import Account from '@/models/Account';
|
||||
import JournalPoster from '@/services/Accounting/JournalPoster';
|
||||
import JournalEntry from '@/services/Accounting/JournalEntry';
|
||||
import ManualJournal from '@/models/ManualJournal';
|
||||
|
||||
export default {
|
||||
/**
|
||||
@@ -57,11 +55,13 @@ export default {
|
||||
const date = moment(form.date).format('YYYY-MM-DD');
|
||||
|
||||
const accountsIds = accounts.map((account) => account.id);
|
||||
|
||||
const { Account, ManualJournal } = req.models;
|
||||
const storedAccounts = await Account.query()
|
||||
.select(['id']).whereIn('id', accountsIds)
|
||||
.withGraphFetched('type');
|
||||
|
||||
const accountsCollection = new Map(storedAccounts.map(i => [i.id, i]));
|
||||
const accountsCollection = new Map(storedAccounts.map((i) => [i.id, i]));
|
||||
|
||||
// Get the stored accounts Ids and difference with submit accounts.
|
||||
const accountsStoredIds = storedAccounts.map((account) => account.id);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import express from 'express';
|
||||
import JWTAuth from '@/http/middleware/jwtAuth';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import AccountType from '@/models/AccountType';
|
||||
|
||||
export default {
|
||||
/**
|
||||
@@ -24,6 +23,7 @@ export default {
|
||||
getAccountTypesList: {
|
||||
validation: [],
|
||||
async handler(req, res) {
|
||||
const { AccountType } = req.models;
|
||||
const accountTypes = await AccountType.query();
|
||||
|
||||
return res.status(200).send({
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import { check, query, oneOf, validationResult, param } from 'express-validator';
|
||||
import { check, query, validationResult, param } from 'express-validator';
|
||||
import express from 'express';
|
||||
import { difference } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import Account from '@/models/Account';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import JWTAuth from '@/http/middleware/jwtAuth';
|
||||
import JournalPoster from '@/services/Accounting/JournalPoster';
|
||||
import JournalEntry from '@/services/Accounting/JournalEntry';
|
||||
import ManualJournal from '@/models/JournalEntry';
|
||||
import AccountTransaction from '@/models/AccountTransaction';
|
||||
import Resource from '@/models/Resource';
|
||||
import View from '@/models/View';
|
||||
import {
|
||||
mapViewRolesToConditionals,
|
||||
mapFilterRolesToDynamicFilter,
|
||||
@@ -55,6 +50,10 @@ export default {
|
||||
this.deleteManualJournal.validation,
|
||||
asyncMiddleware(this.deleteManualJournal.handler));
|
||||
|
||||
router.delete('/manual-journals',
|
||||
this.deleteBulkManualJournals.validation,
|
||||
asyncMiddleware(this.deleteBulkManualJournals.handler));
|
||||
|
||||
router.post('/recurring-journal-entries',
|
||||
this.recurringJournalEntries.validation,
|
||||
asyncMiddleware(this.recurringJournalEntries.handler));
|
||||
@@ -91,6 +90,7 @@ export default {
|
||||
if (filter.stringified_filter_roles) {
|
||||
filter.filter_roles = JSON.parse(filter.stringified_filter_roles);
|
||||
}
|
||||
const { Resource, View, ManualJournal } = req.models;
|
||||
|
||||
const errorReasons = [];
|
||||
const manualJournalsResource = await Resource.query()
|
||||
@@ -200,6 +200,8 @@ export default {
|
||||
reference: '',
|
||||
...req.body,
|
||||
};
|
||||
const { ManualJournal, Account } = req.models;
|
||||
|
||||
let totalCredit = 0;
|
||||
let totalDebit = 0;
|
||||
|
||||
@@ -341,6 +343,10 @@ export default {
|
||||
...req.body,
|
||||
};
|
||||
const { id } = req.params;
|
||||
const {
|
||||
ManualJournal, AccountTransaction, Account,
|
||||
} = req.models;
|
||||
|
||||
const manualJournal = await ManualJournal.query().where('id', id).first();
|
||||
|
||||
if (!manualJournal) {
|
||||
@@ -456,6 +462,11 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const {
|
||||
ManualJournal,
|
||||
AccountTransaction,
|
||||
} = req.models;
|
||||
|
||||
const { id } = req.params;
|
||||
const manualJournal = await ManualJournal.query()
|
||||
.where('id', id).first();
|
||||
@@ -508,6 +519,10 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const {
|
||||
ManualJournal, AccountTransaction,
|
||||
} = req.models;
|
||||
|
||||
const { id } = req.params;
|
||||
const manualJournal = await ManualJournal.query()
|
||||
.where('id', id).first();
|
||||
@@ -549,6 +564,9 @@ export default {
|
||||
});
|
||||
}
|
||||
const { id } = req.params;
|
||||
const {
|
||||
ManualJournal, AccountTransaction,
|
||||
} = req.models;
|
||||
const manualJournal = await ManualJournal.query()
|
||||
.where('id', id).first();
|
||||
|
||||
@@ -614,6 +632,7 @@ export default {
|
||||
}
|
||||
const errorReasons = [];
|
||||
const form = { ...req.body };
|
||||
const { Account } = req.models;
|
||||
|
||||
const foundAccounts = await Account.query()
|
||||
.where('id', form.credit_account_id)
|
||||
@@ -642,4 +661,54 @@ export default {
|
||||
return res.status(200).send();
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Deletes bulk manual journals.
|
||||
*/
|
||||
deleteBulkManualJournals: {
|
||||
validation: [
|
||||
query('ids').isArray({ min: 2 }),
|
||||
query('ids.*').isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const filter = { ...req.query };
|
||||
const { ManualJournal, AccountTransaction } = req.models;
|
||||
|
||||
const manualJournals = await ManualJournal.query()
|
||||
.whereIn('id', filter.ids);
|
||||
|
||||
const notFoundManualJournals = difference(filter.ids, manualJournals.map(m => m.id));
|
||||
|
||||
if (notFoundManualJournals.length > 0) {
|
||||
return res.status(404).send({
|
||||
errors: [{ type: 'MANUAL.JOURNAL.NOT.FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
const transactions = await AccountTransaction.query()
|
||||
.whereIn('reference_type', ['Journal', 'ManualJournal'])
|
||||
.whereIn('reference_id', filter.ids);
|
||||
|
||||
const journal = new JournalPoster();
|
||||
|
||||
journal.loadEntries(transactions);
|
||||
journal.removeEntries();
|
||||
|
||||
await ManualJournal.query()
|
||||
.whereIn('id', filter.ids).delete();
|
||||
|
||||
await Promise.all([
|
||||
journal.deleteEntries(),
|
||||
journal.saveBalance(),
|
||||
]);
|
||||
return res.status(200).send({ ids: filter.ids });
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -7,15 +7,10 @@ import {
|
||||
} from 'express-validator';
|
||||
import { difference } from 'lodash';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import Account from '@/models/Account';
|
||||
import AccountType from '@/models/AccountType';
|
||||
import AccountTransaction from '@/models/AccountTransaction';
|
||||
import JournalPoster from '@/services/Accounting/JournalPoster';
|
||||
import AccountBalance from '@/models/AccountBalance';
|
||||
import NestedSet from '@/collection/NestedSet';
|
||||
import Resource from '@/models/Resource';
|
||||
import View from '@/models/View';
|
||||
import JWTAuth from '@/http/middleware/jwtAuth';
|
||||
import TenancyMiddleware from '@/http/middleware/TenancyMiddleware';
|
||||
import {
|
||||
mapViewRolesToConditionals,
|
||||
mapFilterRolesToDynamicFilter,
|
||||
@@ -36,6 +31,8 @@ export default {
|
||||
const router = express.Router();
|
||||
|
||||
router.use(JWTAuth);
|
||||
router.use(TenancyMiddleware);
|
||||
|
||||
router.post('/',
|
||||
this.newAccount.validation,
|
||||
asyncMiddleware(this.newAccount.handler));
|
||||
@@ -84,8 +81,12 @@ export default {
|
||||
*/
|
||||
newAccount: {
|
||||
validation: [
|
||||
check('name').exists().isLength({ min: 3 }).trim().escape(),
|
||||
check('code').optional().isLength({ max: 10 }).trim().escape(),
|
||||
check('name').exists().isLength({ min: 3 })
|
||||
.trim()
|
||||
.escape(),
|
||||
check('code').optional().isLength({ max: 10 })
|
||||
.trim()
|
||||
.escape(),
|
||||
check('account_type_id').exists().isNumeric().toInt(),
|
||||
check('description').optional().trim().escape(),
|
||||
],
|
||||
@@ -98,6 +99,7 @@ export default {
|
||||
});
|
||||
}
|
||||
const form = { ...req.body };
|
||||
const { AccountType, Account } = req.models;
|
||||
|
||||
const foundAccountCodePromise = form.code
|
||||
? Account.query().where('code', form.code) : null;
|
||||
@@ -131,8 +133,12 @@ export default {
|
||||
editAccount: {
|
||||
validation: [
|
||||
param('id').exists().toInt(),
|
||||
check('name').exists().isLength({ min: 3 }).trim().escape(),
|
||||
check('code').exists().isLength({ max: 10 }).trim().escape(),
|
||||
check('name').exists().isLength({ min: 3 })
|
||||
.trim()
|
||||
.escape(),
|
||||
check('code').exists().isLength({ max: 10 })
|
||||
.trim()
|
||||
.escape(),
|
||||
check('account_type_id').exists().isNumeric().toInt(),
|
||||
check('description').optional().trim().escape(),
|
||||
],
|
||||
@@ -145,6 +151,7 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { Account, AccountType } = req.models;
|
||||
const form = { ...req.body };
|
||||
const account = await Account.query().findById(id);
|
||||
|
||||
@@ -185,6 +192,7 @@ export default {
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { id } = req.params;
|
||||
const { Account } = req.models;
|
||||
const account = await Account.query().where('id', id).first();
|
||||
|
||||
if (!account) {
|
||||
@@ -203,6 +211,7 @@ export default {
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { id } = req.params;
|
||||
const { Account, AccountTransaction } = req.models;
|
||||
const account = await Account.query().findById(id);
|
||||
|
||||
if (!account) {
|
||||
@@ -255,6 +264,8 @@ export default {
|
||||
if (filter.stringified_filter_roles) {
|
||||
filter.filter_roles = JSON.parse(filter.stringified_filter_roles);
|
||||
}
|
||||
|
||||
const { Resource, Account, View } = req.models;
|
||||
const errorReasons = [];
|
||||
|
||||
const accountsResource = await Resource.query()
|
||||
@@ -294,7 +305,7 @@ export default {
|
||||
}
|
||||
|
||||
// View roles.
|
||||
if (view && view.roles.length > 0) {
|
||||
if (view && view.roles.length > 0) {
|
||||
const viewFilter = new DynamicFilterViews(
|
||||
mapViewRolesToConditionals(view.roles),
|
||||
view.rolesLogicExpression,
|
||||
@@ -349,6 +360,11 @@ export default {
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
Account,
|
||||
AccountTransaction,
|
||||
AccountBalance,
|
||||
} = req.models;
|
||||
const account = await Account.findById(id);
|
||||
|
||||
if (!account) {
|
||||
@@ -381,6 +397,7 @@ export default {
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { id } = req.params;
|
||||
const { Account } = req.models;
|
||||
const account = await Account.findById(id);
|
||||
|
||||
if (!account) {
|
||||
@@ -403,6 +420,7 @@ export default {
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { id } = req.params;
|
||||
const { Account } = req.models;
|
||||
const account = await Account.findById(id);
|
||||
|
||||
if (!account) {
|
||||
@@ -461,12 +479,14 @@ export default {
|
||||
});
|
||||
}
|
||||
const filter = { ids: [], ...req.query };
|
||||
const { Account, AccountTransaction } = req.models;
|
||||
|
||||
const accounts = await Account.query().onBuild((builder) => {
|
||||
if (filter.ids.length) {
|
||||
builder.whereIn('id', filter.ids);
|
||||
}
|
||||
});
|
||||
const accountsIds = accounts.map(a => a.id);
|
||||
const accountsIds = accounts.map((a) => a.id);
|
||||
const notFoundAccounts = difference(filter.ids, accountsIds);
|
||||
|
||||
if (notFoundAccounts.length > 0) {
|
||||
|
||||
@@ -5,11 +5,18 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import Mustache from 'mustache';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import asyncMiddleware from '../middleware/asyncMiddleware';
|
||||
import User from '@/models/User';
|
||||
import PasswordReset from '@/models/PasswordReset';
|
||||
import { pick } from 'lodash';
|
||||
import uniqid from 'uniqid';
|
||||
import Logger from '@/services/Logger';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import SystemUser from '@/system/models/SystemUser';
|
||||
import mail from '@/services/mail';
|
||||
import { hashPassword } from '@/utils';
|
||||
import dbManager from '@/database/manager';
|
||||
import Tenant from '@/system/models/Tenant';
|
||||
import TenantUser from '@/models/TenantUser';
|
||||
import TenantsManager from '@/system/TenantsManager';
|
||||
import TenantModel from '@/models/TenantModel';
|
||||
|
||||
export default {
|
||||
/**
|
||||
@@ -22,6 +29,10 @@ export default {
|
||||
this.login.validation,
|
||||
asyncMiddleware(this.login.handler));
|
||||
|
||||
router.post('/register',
|
||||
this.register.validation,
|
||||
asyncMiddleware(this.register.handler));
|
||||
|
||||
router.post('/send_reset_password',
|
||||
this.sendResetPassword.validation,
|
||||
asyncMiddleware(this.sendResetPassword.handler));
|
||||
@@ -49,12 +60,15 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { crediential, password } = req.body;
|
||||
const form = { ...req.body };
|
||||
const { JWT_SECRET_KEY } = process.env;
|
||||
|
||||
const user = await User.query()
|
||||
.where('email', crediential)
|
||||
.orWhere('phone_number', crediential)
|
||||
Logger.log('info', 'Someone trying to login.', { form });
|
||||
|
||||
const user = await SystemUser.query()
|
||||
.withGraphFetched('tenant')
|
||||
.where('email', form.crediential)
|
||||
.orWhere('phone_number', form.crediential)
|
||||
.first();
|
||||
|
||||
if (!user) {
|
||||
@@ -62,7 +76,7 @@ export default {
|
||||
errors: [{ type: 'INVALID_DETAILS', code: 100 }],
|
||||
});
|
||||
}
|
||||
if (!user.verifyPassword(password)) {
|
||||
if (!user.verifyPassword(form.password)) {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'INVALID_DETAILS', code: 100 }],
|
||||
});
|
||||
@@ -74,16 +88,89 @@ export default {
|
||||
}
|
||||
// user.update({ last_login_at: new Date() });
|
||||
|
||||
const token = jwt.sign({
|
||||
email: user.email,
|
||||
_id: user.id,
|
||||
}, JWT_SECRET_KEY, {
|
||||
expiresIn: '1d',
|
||||
});
|
||||
const token = jwt.sign(
|
||||
{ email: user.email, _id: user.id },
|
||||
JWT_SECRET_KEY,
|
||||
{ expiresIn: '1d' },
|
||||
);
|
||||
Logger.log('info', 'Logging success.', { form });
|
||||
|
||||
return res.status(200).send({ token, user });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers a new organization.
|
||||
*/
|
||||
register: {
|
||||
validation: [
|
||||
check('organization_name').exists().trim().escape(),
|
||||
check('first_name').exists().trim().escape(),
|
||||
check('last_name').exists().trim().escape(),
|
||||
check('email').exists().trim().escape(),
|
||||
check('phone_number').exists().trim().escape(),
|
||||
check('password').exists().trim().escape(),
|
||||
check('country').exists().trim().escape(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const form = { ...req.body };
|
||||
Logger.log('info', 'Someone trying to register.', { form });
|
||||
|
||||
const user = await SystemUser.query()
|
||||
.where('email', form.email)
|
||||
.orWhere('phone_number', form.phone_number)
|
||||
.first();
|
||||
|
||||
if (user && user.phoneNumber === form.phone_number) {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'PHONE_NUMBER_EXISTS', code: 100 }],
|
||||
});
|
||||
}
|
||||
if (user && user.email === form.email) {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'EMAIL_EXISTS', code: 200 }],
|
||||
});
|
||||
}
|
||||
const organizationId = uniqid();
|
||||
const tenantOrganization = await Tenant.query().insert({
|
||||
organization_id: organizationId,
|
||||
});
|
||||
|
||||
const hashedPassword = await hashPassword(form.password);
|
||||
const userInsert = {
|
||||
...pick(form, ['first_name', 'last_name', 'email', 'phone_number']),
|
||||
password: hashedPassword,
|
||||
active: true,
|
||||
};
|
||||
const registeredUser = await SystemUser.query().insert({
|
||||
...userInsert,
|
||||
tenant_id: tenantOrganization.id,
|
||||
});
|
||||
await dbManager.createDb(`bigcapital_tenant_${organizationId}`);
|
||||
|
||||
const tenantDb = TenantsManager.knexInstance(organizationId);
|
||||
await tenantDb.migrate.latest();
|
||||
|
||||
TenantModel.knexBinded = tenantDb;
|
||||
|
||||
await TenantUser.bindKnex(tenantDb).query().insert({
|
||||
...userInsert,
|
||||
});
|
||||
Logger.log('info', 'New tenant has been created.', { organizationId });
|
||||
|
||||
return res.status(200).send({
|
||||
organization_id: organizationId,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Send reset password link via email or SMS.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import express from 'express';
|
||||
import { check, param, validationResult } from 'express-validator';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import Currency from '@/models/Currency';
|
||||
import jwtAuth from '@/http/middleware/jwtAuth';
|
||||
|
||||
export default {
|
||||
@@ -17,7 +16,7 @@ export default {
|
||||
router.post('/',
|
||||
this.newCurrency.validation,
|
||||
asyncMiddleware(this.newCurrency.handler));
|
||||
|
||||
|
||||
router.post('/:id',
|
||||
this.editCurrency.validation,
|
||||
asyncMiddleware(this.editCurrency.handler));
|
||||
@@ -35,6 +34,7 @@ export default {
|
||||
all: {
|
||||
validation: [],
|
||||
async handler(req, res) {
|
||||
const { Currency } = req.models;
|
||||
const currencies = await Currency.query();
|
||||
|
||||
return res.status(200).send({
|
||||
@@ -59,6 +59,7 @@ export default {
|
||||
});
|
||||
}
|
||||
const form = { ...req.body };
|
||||
const { Currency } = req.models;
|
||||
|
||||
const foundCurrency = await Currency.query()
|
||||
.where('currency_code', form.currency_code);
|
||||
@@ -89,6 +90,7 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { Currency } = req.models;
|
||||
const { currency_code: currencyCode } = req.params;
|
||||
|
||||
await Currency.query()
|
||||
@@ -115,23 +117,19 @@ export default {
|
||||
}
|
||||
const form = { ...req.body };
|
||||
const { id } = req.params;
|
||||
const { Currency } = req.models;
|
||||
|
||||
const foundCurrency = await Currency.query()
|
||||
.where('currency_code', form.currency_code)
|
||||
.whereNot('id', id);
|
||||
.where('currency_code', form.currency_code).whereNot('id', id);
|
||||
|
||||
if (foundCurrency.length > 0) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'CURRENCY.CODE.ALREADY.EXISTS', code: 100 }],
|
||||
});
|
||||
}
|
||||
await Currency.query()
|
||||
.where('id', id)
|
||||
.update({ ...form });
|
||||
await Currency.query().where('id', id).update({ ...form });
|
||||
|
||||
return res.status(200).send({
|
||||
currency: { ...form },
|
||||
});
|
||||
return res.status(200).send({ currency: { ...form } });
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import express from 'express';
|
||||
import { check, param, query, validationResult } from 'express-validator';
|
||||
import {
|
||||
check,
|
||||
param,
|
||||
query,
|
||||
validationResult,
|
||||
} from 'express-validator';
|
||||
import moment from 'moment';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import jwtAuth from '@/http/middleware/jwtAuth';
|
||||
import ExchangeRate from '@/models/ExchangeRate';
|
||||
|
||||
export default {
|
||||
/**
|
||||
@@ -53,11 +57,12 @@ export default {
|
||||
page_size: 10,
|
||||
...req.query,
|
||||
};
|
||||
const { ExchangeRate } = req.models;
|
||||
const exchangeRates = await ExchangeRate.query()
|
||||
.pagination(filter.page - 1, filter.page_size);
|
||||
|
||||
return res.status(200).send({ exchange_rates: exchangeRates });
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -77,7 +82,7 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
|
||||
const { ExchangeRate } = req.models;
|
||||
const form = { ...req.body };
|
||||
const foundExchangeRate = await ExchangeRate.query()
|
||||
.where('currency_code', form.currency_code)
|
||||
@@ -87,7 +92,7 @@ export default {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'EXCHANGE.RATE.DATE.PERIOD.DEFINED', code: 200 }],
|
||||
});
|
||||
}
|
||||
}
|
||||
await ExchangeRate.query().insert({
|
||||
...form,
|
||||
date: moment(form.date).format('YYYY-MM-DD'),
|
||||
@@ -116,6 +121,7 @@ export default {
|
||||
}
|
||||
const { id } = req.params;
|
||||
const form = { ...req.body };
|
||||
const { ExchangeRate } = req.models;
|
||||
|
||||
const foundExchangeRate = await ExchangeRate.query()
|
||||
.where('id', id);
|
||||
@@ -148,19 +154,18 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { id } = req.params;
|
||||
const foundExchangeRate = await ExchangeRate.query()
|
||||
.where('id', id);
|
||||
const { id } = req.params;
|
||||
const { ExchangeRate } = req.models;
|
||||
const foundExchangeRate = await ExchangeRate.query().where('id', id);
|
||||
|
||||
if (!foundExchangeRate.length) {
|
||||
return res.status(404).send({
|
||||
errors: [{ type: 'EXCHANGE.RATE.NOT.FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
await ExchangeRate.query()
|
||||
.where('id', id).delete();
|
||||
await ExchangeRate.query().where('id', id).delete();
|
||||
|
||||
return res.status(200).send({ id });
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -8,14 +8,9 @@ import {
|
||||
import moment from 'moment';
|
||||
import { difference, chain, omit } from 'lodash';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import Expense from '@/models/Expense';
|
||||
import Account from '@/models/Account';
|
||||
import JournalPoster from '@/services/Accounting/JournalPoster';
|
||||
import JournalEntry from '@/services/Accounting/JournalEntry';
|
||||
import JWTAuth from '@/http/middleware/jwtAuth';
|
||||
import AccountTransaction from '@/models/AccountTransaction';
|
||||
import View from '@/models/View';
|
||||
import Resource from '../../models/Resource';
|
||||
import ResourceCustomFieldRepository from '@/services/CustomFields/ResourceCustomFieldRepository';
|
||||
import {
|
||||
validateViewRoles,
|
||||
@@ -92,6 +87,7 @@ export default {
|
||||
custom_fields: [],
|
||||
...req.body,
|
||||
};
|
||||
const { Account, Expense } = req.models;
|
||||
// Convert the date to the general format.
|
||||
form.date = moment(form.date).format('YYYY-MM-DD');
|
||||
|
||||
@@ -174,6 +170,7 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { Account, Expense } = req.models;
|
||||
const form = { ...req.body };
|
||||
const errorReasons = [];
|
||||
|
||||
@@ -268,6 +265,7 @@ export default {
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const { Expense, AccountTransaction } = req.models;
|
||||
const errorReasons = [];
|
||||
const expense = await Expense.query().findById(id);
|
||||
|
||||
@@ -277,7 +275,6 @@ export default {
|
||||
if (errorReasons.length > 0) {
|
||||
return res.status(400).send({ errors: errorReasons });
|
||||
}
|
||||
|
||||
if (expense.published) {
|
||||
errorReasons.push({ type: 'EXPENSE.ALREADY.PUBLISHED', code: 200 });
|
||||
}
|
||||
@@ -337,6 +334,7 @@ export default {
|
||||
page: 1,
|
||||
...req.query,
|
||||
};
|
||||
const { Resource, View, Expense } = req.models;
|
||||
const errorReasons = [];
|
||||
const expenseResource = await Resource.query().where('name', 'expenses').first();
|
||||
|
||||
@@ -364,7 +362,7 @@ export default {
|
||||
viewConditionals = mapViewRolesToConditionals(view.viewRoles);
|
||||
|
||||
if (!validateViewRoles(viewConditionals, view.rolesLogicExpression)) {
|
||||
errorReasons.push({ type: 'VIEW.LOGIC.EXPRESSION.INVALID', code: 400 })
|
||||
errorReasons.push({ type: 'VIEW.LOGIC.EXPRESSION.INVALID', code: 400 });
|
||||
}
|
||||
}
|
||||
if (!view && filter.custom_view_id) {
|
||||
@@ -391,7 +389,7 @@ export default {
|
||||
|
||||
return res.status(200).send({
|
||||
...(view) ? {
|
||||
customViewId: view.id,
|
||||
customViewId: view.id,
|
||||
viewColumns: view.columns,
|
||||
viewConditionals,
|
||||
} : {},
|
||||
@@ -416,6 +414,7 @@ export default {
|
||||
});
|
||||
}
|
||||
const { id } = req.params;
|
||||
const { Expense, AccountTransaction } = req.models;
|
||||
const expenseTransaction = await Expense.query().findById(id);
|
||||
|
||||
if (!expenseTransaction) {
|
||||
@@ -463,6 +462,7 @@ export default {
|
||||
});
|
||||
}
|
||||
const { id } = req.params;
|
||||
const { Expense } = req.models;
|
||||
const expenseTransaction = await Expense.query().findById(id);
|
||||
|
||||
if (!expenseTransaction) {
|
||||
@@ -489,6 +489,7 @@ export default {
|
||||
});
|
||||
}
|
||||
const { id } = req.params;
|
||||
const { Expense } = req.models;
|
||||
const expenseTransaction = await Expense.query().findById(id);
|
||||
|
||||
if (!expenseTransaction) {
|
||||
|
||||
@@ -3,10 +3,7 @@ import { query, oneOf, validationResult } from 'express-validator';
|
||||
import moment from 'moment';
|
||||
import { pick, difference, groupBy } from 'lodash';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import AccountTransaction from '@/models/AccountTransaction';
|
||||
import jwtAuth from '@/http/middleware/jwtAuth';
|
||||
import AccountType from '@/models/AccountType';
|
||||
import Account from '@/models/Account';
|
||||
import JournalPoster from '@/services/Accounting/JournalPoster';
|
||||
import { dateRangeCollection } from '@/utils';
|
||||
|
||||
@@ -89,6 +86,7 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { AccountTransaction } = req.models;
|
||||
const filter = {
|
||||
from_date: moment().startOf('year').format('YYYY-MM-DD'),
|
||||
to_date: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
@@ -119,9 +117,9 @@ export default {
|
||||
|
||||
const formatNumber = formatNumberClosure(filter.number_format);
|
||||
|
||||
const journalGrouped = groupBy(accountsJournalEntries, (entry) => {
|
||||
return `${entry.referenceId}-${entry.referenceType}`;
|
||||
});
|
||||
const journalGrouped = groupBy(accountsJournalEntries,
|
||||
(entry) => `${entry.referenceId}-${entry.referenceType}`);
|
||||
|
||||
const journal = Object.keys(journalGrouped).map((key) => {
|
||||
const transactionsGroup = journalGrouped[key];
|
||||
|
||||
@@ -169,6 +167,7 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { AccountTransaction, Account } = req.models;
|
||||
const filter = {
|
||||
from_date: moment().startOf('year').format('YYYY-MM-DD'),
|
||||
to_date: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
@@ -289,6 +288,7 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { Account, AccountType } = req.models;
|
||||
const filter = {
|
||||
display_columns_type: 'total',
|
||||
display_columns_by: '',
|
||||
@@ -327,22 +327,20 @@ export default {
|
||||
filter.from_date, filter.to_date, comparatorDateType,
|
||||
) : [];
|
||||
|
||||
const totalPeriods = (account) => {
|
||||
const totalPeriods = (account) => ({
|
||||
// Gets the date range set from start to end date.
|
||||
return {
|
||||
total_periods: dateRangeSet.map((date) => {
|
||||
const balance = journalEntries.getClosingBalance(account.id, date, comparatorDateType);
|
||||
return {
|
||||
date,
|
||||
formatted_amount: balanceFormatter(balance),
|
||||
amount: balance,
|
||||
};
|
||||
}),
|
||||
};
|
||||
};
|
||||
total_periods: dateRangeSet.map((date) => {
|
||||
const balance = journalEntries.getClosingBalance(account.id, date, comparatorDateType);
|
||||
return {
|
||||
date,
|
||||
formatted_amount: balanceFormatter(balance),
|
||||
amount: balance,
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
const accountsMapper = (balanceSheetAccounts) => {
|
||||
return balanceSheetAccounts.map((account) => {
|
||||
const accountsMapper = (balanceSheetAccounts) => [
|
||||
...balanceSheetAccounts.map((account) => {
|
||||
// Calculates the closing balance to the given date.
|
||||
const closingBalance = journalEntries.getClosingBalance(account.id, filter.to_date);
|
||||
|
||||
@@ -358,8 +356,8 @@ export default {
|
||||
date: filter.to_date,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
}),
|
||||
];
|
||||
// Retrieve all assets accounts.
|
||||
const assetsAccounts = accounts.filter((account) => (
|
||||
account.type.normal === 'debit'
|
||||
@@ -414,6 +412,9 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const {
|
||||
Account,
|
||||
} = req.models;
|
||||
const filter = {
|
||||
from_date: moment().startOf('year').format('YYYY-MM-DD'),
|
||||
to_date: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
@@ -490,6 +491,7 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { Account, AccountType } = req.models;
|
||||
const filter = {
|
||||
from_date: moment().startOf('year').format('YYYY-MM-DD'),
|
||||
to_date: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
|
||||
@@ -3,13 +3,6 @@ import { check, query, validationResult } from 'express-validator';
|
||||
import { difference } from 'lodash';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import jwtAuth from '@/http/middleware/jwtAuth';
|
||||
import Item from '@/models/Item';
|
||||
import Account from '@/models/Account';
|
||||
import ItemCategory from '@/models/ItemCategory';
|
||||
import Resource from '@/models/Resource';
|
||||
import ResourceField from '@/models/ResourceField';
|
||||
import Authorization from '@/http/middleware/authorization';
|
||||
import View from '@/models/View';
|
||||
import {
|
||||
mapViewRolesToConditionals,
|
||||
mapFilterRolesToDynamicFilter,
|
||||
@@ -23,10 +16,11 @@ import {
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
const router = express.Router();
|
||||
const permit = Authorization('items');
|
||||
|
||||
router.use(jwtAuth);
|
||||
|
||||
@@ -35,7 +29,6 @@ export default {
|
||||
asyncMiddleware(this.editItem.handler));
|
||||
|
||||
router.post('/',
|
||||
// permit('create'),
|
||||
this.newItem.validation,
|
||||
asyncMiddleware(this.newItem.handler));
|
||||
|
||||
@@ -43,10 +36,6 @@ export default {
|
||||
this.deleteItem.validation,
|
||||
asyncMiddleware(this.deleteItem.handler));
|
||||
|
||||
// router.get('/:id',
|
||||
// this.getCategory.validation,
|
||||
// asyncMiddleware(this.getCategory.handler));
|
||||
|
||||
router.get('/',
|
||||
this.listItems.validation,
|
||||
asyncMiddleware(this.listItems.handler));
|
||||
@@ -92,12 +81,19 @@ export default {
|
||||
custom_fields: [],
|
||||
...req.body,
|
||||
};
|
||||
const {
|
||||
Account,
|
||||
Resource,
|
||||
ResourceField,
|
||||
ItemCategory,
|
||||
Item,
|
||||
} = req.models;
|
||||
const errorReasons = [];
|
||||
|
||||
const costAccountPromise = Account.query().findById(form.cost_account_id);
|
||||
const sellAccountPromise = Account.query().findById(form.sell_account_id);
|
||||
const inventoryAccountPromise = (form.type === 'inventory') ?
|
||||
Account.query().findByid(form.inventory_account_id) : null;
|
||||
const inventoryAccountPromise = (form.type === 'inventory')
|
||||
? Account.query().findByid(form.inventory_account_id) : null;
|
||||
|
||||
const itemCategoryPromise = (form.category_id)
|
||||
? ItemCategory.query().findById(form.category_id) : null;
|
||||
@@ -108,9 +104,9 @@ export default {
|
||||
|
||||
// Get resource id than get all resource fields.
|
||||
const resource = await Resource.where('name', 'items').fetch();
|
||||
const fields = await ResourceField.query((query) => {
|
||||
query.where('resource_id', resource.id);
|
||||
query.whereIn('key', customFieldsKeys);
|
||||
const fields = await ResourceField.query((builder) => {
|
||||
builder.where('resource_id', resource.id);
|
||||
builder.whereIn('key', customFieldsKeys);
|
||||
}).fetchAll();
|
||||
|
||||
const storedFieldsKey = fields.map((f) => f.attributes.key);
|
||||
@@ -186,18 +182,19 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
|
||||
const { Account, Item, ItemCategory } = req.models;
|
||||
const { id } = req.params;
|
||||
|
||||
const form = {
|
||||
custom_fields: [],
|
||||
...req.body,
|
||||
};
|
||||
const item = await Item.query().findById(id);
|
||||
|
||||
|
||||
if (!item) {
|
||||
return res.boom.notFound(null, { errors: [
|
||||
{ type: 'ITEM.NOT.FOUND', code: 100 },
|
||||
]});
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'ITEM.NOT.FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
const errorReasons = [];
|
||||
|
||||
@@ -244,6 +241,7 @@ export default {
|
||||
validation: [],
|
||||
async handler(req, res) {
|
||||
const { id } = req.params;
|
||||
const { Item } = req.models;
|
||||
const item = await Item.query().findById(id);
|
||||
|
||||
if (!item) {
|
||||
@@ -251,7 +249,6 @@ export default {
|
||||
errors: [{ type: 'ITEM_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
|
||||
// Delete the fucking the given item id.
|
||||
await Item.query().findById(item.id).delete();
|
||||
|
||||
@@ -281,15 +278,16 @@ export default {
|
||||
}
|
||||
const errorReasons = [];
|
||||
const viewConditions = [];
|
||||
const { Resource, Item, View } = req.models;
|
||||
const itemsResource = await Resource.query()
|
||||
.where('name', 'items')
|
||||
.withGraphFetched('fields')
|
||||
.first();
|
||||
|
||||
if (!itemsResource) {
|
||||
return res.status(400).send({ errors: [
|
||||
{type: 'ITEMS_RESOURCE_NOT_FOUND', code: 200},
|
||||
]});
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'ITEMS_RESOURCE_NOT_FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
const filter = {
|
||||
column_sort_order: '',
|
||||
@@ -377,4 +375,4 @@ export default {
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ import express from 'express';
|
||||
import { body, query, validationResult } from 'express-validator';
|
||||
import { pick } from 'lodash';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import Option from '@/models/Option';
|
||||
import jwtAuth from '@/http/middleware/jwtAuth';
|
||||
|
||||
export default {
|
||||
@@ -43,6 +42,7 @@ export default {
|
||||
code: 'VALIDATION_ERROR', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { Option } = req.models;
|
||||
const form = { ...req.body };
|
||||
const optionsCollections = await Option.query();
|
||||
|
||||
@@ -53,7 +53,7 @@ export default {
|
||||
errorReasons.push({
|
||||
type: 'OPTIONS.KEY.NOT.DEFINED',
|
||||
code: 200,
|
||||
keys: notDefinedOptions.map(o => ({ ...pick(o, ['key', 'group']) })),
|
||||
keys: notDefinedOptions.map((o) => ({ ...pick(o, ['key', 'group']) })),
|
||||
});
|
||||
}
|
||||
if (errorReasons.length) {
|
||||
@@ -84,6 +84,7 @@ export default {
|
||||
code: 'VALIDATION_ERROR', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { Option } = req.models;
|
||||
const filter = { ...req.query };
|
||||
const options = await Option.query().onBuild((builder) => {
|
||||
if (filter.key) {
|
||||
@@ -93,7 +94,6 @@ export default {
|
||||
builder.where('group', filter.group);
|
||||
}
|
||||
});
|
||||
|
||||
return res.status(200).send({ options: options.metadata });
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,11 +2,9 @@ import express from 'express';
|
||||
import {
|
||||
param,
|
||||
query,
|
||||
validationResult,
|
||||
} from 'express-validator';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import jwtAuth from '@/http/middleware/jwtAuth';
|
||||
import Resource from '@/models/Resource';
|
||||
|
||||
export default {
|
||||
/**
|
||||
@@ -37,6 +35,7 @@ export default {
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { resource_slug: resourceSlug } = req.params;
|
||||
const { Resource } = req.models;
|
||||
|
||||
const resource = await Resource.query()
|
||||
.where('name', resourceSlug)
|
||||
@@ -74,6 +73,7 @@ export default {
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { resource_slug: resourceSlug } = req.params;
|
||||
const { Resource } = req.models;
|
||||
|
||||
const resource = await Resource.query()
|
||||
.where('name', resourceSlug)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { difference, intersection, pick } from 'lodash';
|
||||
import { difference, pick } from 'lodash';
|
||||
import express from 'express';
|
||||
import {
|
||||
check,
|
||||
@@ -9,10 +9,6 @@ import {
|
||||
} from 'express-validator';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import jwtAuth from '@/http/middleware/jwtAuth';
|
||||
import Resource from '@/models/Resource';
|
||||
import View from '@/models/View';
|
||||
import ViewRole from '@/models/ViewRole';
|
||||
import ViewColumn from '@/models/ViewColumn';
|
||||
import {
|
||||
validateViewRoles,
|
||||
} from '@/lib/ViewRolesBuilder';
|
||||
@@ -62,6 +58,7 @@ export default {
|
||||
]),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { Resource, View } = req.models;
|
||||
const filter = { ...req.query };
|
||||
|
||||
const resource = await Resource.query().onBuild((builder) => {
|
||||
@@ -113,6 +110,7 @@ export default {
|
||||
param('view_id').exists().isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { View } = req.models;
|
||||
const { view_id: viewId } = req.params;
|
||||
const view = await View.query().findById(viewId);
|
||||
|
||||
@@ -161,6 +159,12 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const {
|
||||
Resource,
|
||||
View,
|
||||
ViewColumn,
|
||||
ViewRole,
|
||||
} = req.models;
|
||||
const form = { roles: [], ...req.body };
|
||||
const resource = await Resource.query().where('name', form.resource_name).first();
|
||||
|
||||
@@ -265,6 +269,9 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const {
|
||||
View, ViewRole, ViewColumn, Resource,
|
||||
} = req.models;
|
||||
const view = await View.query().where('id', viewId)
|
||||
.withGraphFetched('roles.field')
|
||||
.withGraphFetched('columns')
|
||||
|
||||
Reference in New Issue
Block a user