Compare commits

..

1 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
882fc20ac1 feat(webapp): bank rules 2024-06-25 11:41:31 +02:00
623 changed files with 3176 additions and 18841 deletions

View File

@@ -132,33 +132,6 @@
"contributions": [ "contributions": [
"bug" "bug"
] ]
},
{
"login": "oleynikd",
"name": "Denis",
"avatar_url": "https://avatars.githubusercontent.com/u/3976868?v=4",
"profile": "https://github.com/oleynikd",
"contributions": [
"bug"
]
},
{
"login": "mittalsam98",
"name": "Sachin Mittal",
"avatar_url": "https://avatars.githubusercontent.com/u/42431274?v=4",
"profile": "https://myself.vercel.app/",
"contributions": [
"bug"
]
},
{
"login": "Champetaman",
"name": "Camilo Oviedo",
"avatar_url": "https://avatars.githubusercontent.com/u/64604272?v=4",
"profile": "https://www.camilooviedo.com/",
"contributions": [
"code"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View File

@@ -2,14 +2,6 @@
All notable changes to Bigcapital server-side will be in this file. All notable changes to Bigcapital server-side will be in this file.
## [v0.18.0] - 10-08-2024
* feat: Bank rules for automated categorization by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
* feat: Categorize & match bank transaction by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
* feat: Reconcile match transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/522
* fix: Issues in matching transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/523
* fix: Cashflow transactions types by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/524
## [v0.17.5] - 17-06-2024 ## [v0.17.5] - 17-06-2024
* fix: Balance sheet and P/L nested accounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/501 * fix: Balance sheet and P/L nested accounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/501

View File

@@ -126,11 +126,6 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="http://vederis.id"><img src="https://avatars.githubusercontent.com/u/13505006?v=4?s=100" width="100px;" alt="Vederis Leunardus"/><br /><sub><b>Vederis Leunardus</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=cloudsbird" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="http://vederis.id"><img src="https://avatars.githubusercontent.com/u/13505006?v=4?s=100" width="100px;" alt="Vederis Leunardus"/><br /><sub><b>Vederis Leunardus</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=cloudsbird" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.pivoten.com"><img src="https://avatars.githubusercontent.com/u/104120598?v=4?s=100" width="100px;" alt="Chris Cantrell"/><br /><sub><b>Chris Cantrell</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Accantrell72" title="Bug reports">🐛</a></td> <td align="center" valign="top" width="14.28%"><a href="http://www.pivoten.com"><img src="https://avatars.githubusercontent.com/u/104120598?v=4?s=100" width="100px;" alt="Chris Cantrell"/><br /><sub><b>Chris Cantrell</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Accantrell72" title="Bug reports">🐛</a></td>
</tr> </tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/oleynikd"><img src="https://avatars.githubusercontent.com/u/3976868?v=4?s=100" width="100px;" alt="Denis"/><br /><sub><b>Denis</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aoleynikd" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://myself.vercel.app/"><img src="https://avatars.githubusercontent.com/u/42431274?v=4?s=100" width="100px;" alt="Sachin Mittal"/><br /><sub><b>Sachin Mittal</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Amittalsam98" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.camilooviedo.com/"><img src="https://avatars.githubusercontent.com/u/64604272?v=4?s=100" width="100px;" alt="Camilo Oviedo"/><br /><sub><b>Camilo Oviedo</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=Champetaman" title="Code">💻</a></td>
</tr>
</tbody> </tbody>
</table> </table>

View File

@@ -103,20 +103,24 @@ export default class AccountsController extends BaseController {
check('name') check('name')
.exists() .exists()
.isLength({ min: 3, max: DATATYPES_LENGTH.STRING }) .isLength({ min: 3, max: DATATYPES_LENGTH.STRING })
.trim(), .trim()
.escape(),
check('code') check('code')
.optional({ nullable: true }) .optional({ nullable: true })
.isLength({ min: 3, max: 6 }) .isLength({ min: 3, max: 6 })
.trim(), .trim()
.escape(),
check('currency_code').optional(), check('currency_code').optional(),
check('account_type') check('account_type')
.exists() .exists()
.isLength({ min: 3, max: DATATYPES_LENGTH.STRING }) .isLength({ min: 3, max: DATATYPES_LENGTH.STRING })
.trim(), .trim()
.escape(),
check('description') check('description')
.optional({ nullable: true }) .optional({ nullable: true })
.isLength({ max: DATATYPES_LENGTH.TEXT }) .isLength({ max: DATATYPES_LENGTH.TEXT })
.trim(), .trim()
.escape(),
check('parent_account_id') check('parent_account_id')
.optional({ nullable: true }) .optional({ nullable: true })
.isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 }) .isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 })
@@ -132,19 +136,23 @@ export default class AccountsController extends BaseController {
check('name') check('name')
.exists() .exists()
.isLength({ min: 3, max: DATATYPES_LENGTH.STRING }) .isLength({ min: 3, max: DATATYPES_LENGTH.STRING })
.trim(), .trim()
.escape(),
check('code') check('code')
.optional({ nullable: true }) .optional({ nullable: true })
.isLength({ min: 3, max: 6 }) .isLength({ min: 3, max: 6 })
.trim(), .trim()
.escape(),
check('account_type') check('account_type')
.exists() .exists()
.isLength({ min: 3, max: DATATYPES_LENGTH.STRING }) .isLength({ min: 3, max: DATATYPES_LENGTH.STRING })
.trim(), .trim()
.escape(),
check('description') check('description')
.optional({ nullable: true }) .optional({ nullable: true })
.isLength({ max: DATATYPES_LENGTH.TEXT }) .isLength({ max: DATATYPES_LENGTH.TEXT })
.trim(), .trim()
.escape(),
check('parent_account_id') check('parent_account_id')
.optional({ nullable: true }) .optional({ nullable: true })
.isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 }) .isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 })

View File

@@ -250,12 +250,10 @@ export class AttachmentsController extends BaseController {
res: Response, res: Response,
next: NextFunction next: NextFunction
): Promise<Response | void> { ): Promise<Response | void> {
const { tenantId } = req;
const { id: documentKey } = req.params; const { id: documentKey } = req.params;
try { try {
const presignedUrl = await this.attachmentsApplication.getPresignedUrl( const presignedUrl = await this.attachmentsApplication.getPresignedUrl(
tenantId,
documentKey documentKey
); );
return res.status(200).send({ presignedUrl }); return res.status(200).send({ presignedUrl });

View File

@@ -90,23 +90,27 @@ export default class AuthenticationController extends BaseController {
.exists() .exists()
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('last_name') check('last_name')
.exists() .exists()
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('email') check('email')
.exists() .exists()
.isString() .isString()
.isEmail() .isEmail()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('password') check('password')
.exists() .exists()
.isString() .isString()
.isLength({ min: 6 }) .isLength({ min: 6 })
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
]; ];
} }
@@ -146,7 +150,7 @@ export default class AuthenticationController extends BaseController {
* @returns {ValidationChain[]} * @returns {ValidationChain[]}
*/ */
private get sendResetPasswordSchema(): ValidationChain[] { private get sendResetPasswordSchema(): ValidationChain[] {
return [check('email').exists().isEmail().trim()]; return [check('email').exists().isEmail().trim().escape()];
} }
/** /**
@@ -154,11 +158,7 @@ export default class AuthenticationController extends BaseController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
private async login( private async login(req: Request, res: Response, next: Function): Response {
req: Request,
res: Response,
next: Function
): Promise<Response | null> {
const userDTO: ILoginDTO = this.matchedBodyData(req); const userDTO: ILoginDTO = this.matchedBodyData(req);
try { try {

View File

@@ -1,218 +0,0 @@
import { Inject, Service } from 'typedi';
import { NextFunction, Request, Response, Router } from 'express';
import { param, query } from 'express-validator';
import BaseController from '@/api/controllers/BaseController';
import { GetBankAccountSummary } from '@/services/Banking/BankAccounts/GetBankAccountSummary';
import { BankAccountsApplication } from '@/services/Banking/BankAccounts/BankAccountsApplication';
import { GetPendingBankAccountTransactions } from '@/services/Cashflow/GetPendingBankAccountTransaction';
@Service()
export class BankAccountsController extends BaseController {
@Inject()
private getBankAccountSummaryService: GetBankAccountSummary;
@Inject()
private bankAccountsApp: BankAccountsApplication;
@Inject()
private getPendingTransactionsService: GetPendingBankAccountTransactions;
/**
* Router constructor.
*/
router() {
const router = Router();
router.get('/:bankAccountId/meta', this.getBankAccountSummary.bind(this));
router.get(
'/pending_transactions',
[
query('account_id').optional().isNumeric().toInt(),
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
],
this.validationResult,
this.getBankAccountsPendingTransactions.bind(this)
);
router.post(
'/:bankAccountId/disconnect',
this.disconnectBankAccount.bind(this)
);
router.post('/:bankAccountId/update', this.refreshBankAccount.bind(this));
router.post(
'/:bankAccountId/pause_feeds',
[param('bankAccountId').exists().isNumeric().toInt()],
this.validationResult,
this.pauseBankAccountFeeds.bind(this)
);
router.post(
'/:bankAccountId/resume_feeds',
[param('bankAccountId').exists().isNumeric().toInt()],
this.validationResult,
this.resumeBankAccountFeeds.bind(this)
);
return router;
}
/**
* Retrieves the bank account meta summary.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
async getBankAccountSummary(
req: Request<{ bankAccountId: number }>,
res: Response,
next: NextFunction
) {
const { bankAccountId } = req.params;
const { tenantId } = req;
try {
const data =
await this.getBankAccountSummaryService.getBankAccountSummary(
tenantId,
bankAccountId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
}
/**
* Retrieves the bank account pending transactions.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async getBankAccountsPendingTransactions(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const query = this.matchedQueryData(req);
try {
const data =
await this.getPendingTransactionsService.getPendingTransactions(
tenantId,
query
);
return res.status(200).send(data);
} catch (error) {
next(error);
}
}
/**
* Disonnect the given bank account.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
async disconnectBankAccount(
req: Request<{ bankAccountId: number }>,
res: Response,
next: NextFunction
) {
const { bankAccountId } = req.params;
const { tenantId } = req;
try {
await this.bankAccountsApp.disconnectBankAccount(tenantId, bankAccountId);
return res.status(200).send({
id: bankAccountId,
message: 'The bank account has been disconnected.',
});
} catch (error) {
next(error);
}
}
/**
* Refresh the given bank account.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
async refreshBankAccount(
req: Request<{ bankAccountId: number }>,
res: Response,
next: NextFunction
) {
const { bankAccountId } = req.params;
const { tenantId } = req;
try {
await this.bankAccountsApp.refreshBankAccount(tenantId, bankAccountId);
return res.status(200).send({
id: bankAccountId,
message: 'The bank account has been disconnected.',
});
} catch (error) {
next(error);
}
}
/**
* Resumes the bank account feeds sync.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response | void>}
*/
async resumeBankAccountFeeds(
req: Request<{ bankAccountId: number }>,
res: Response,
next: NextFunction
) {
const { bankAccountId } = req.params;
const { tenantId } = req;
try {
await this.bankAccountsApp.resumeBankAccount(tenantId, bankAccountId);
return res.status(200).send({
message: 'The bank account feeds syncing has been resumed.',
id: bankAccountId,
});
} catch (error) {
next(error);
}
}
/**
* Pauses the bank account feeds sync.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response | void>}
*/
async pauseBankAccountFeeds(
req: Request<{ bankAccountId: number }>,
res: Response,
next: NextFunction
) {
const { bankAccountId } = req.params;
const { tenantId } = req;
try {
await this.bankAccountsApp.pauseBankAccount(tenantId, bankAccountId);
return res.status(200).send({
message: 'The bank account feeds syncing has been paused.',
id: bankAccountId,
});
} catch (error) {
next(error);
}
}
}

View File

@@ -1,101 +0,0 @@
import { Inject, Service } from 'typedi';
import { body, param } from 'express-validator';
import { NextFunction, Request, Response, Router } from 'express';
import BaseController from '@/api/controllers/BaseController';
import { MatchBankTransactionsApplication } from '@/services/Banking/Matching/MatchBankTransactionsApplication';
@Service()
export class BankTransactionsMatchingController extends BaseController {
@Inject()
private bankTransactionsMatchingApp: MatchBankTransactionsApplication;
/**
* Router constructor.
*/
public router() {
const router = Router();
router.post(
'/unmatch/:transactionId',
[param('transactionId').exists()],
this.validationResult,
this.unmatchMatchedBankTransaction.bind(this)
);
router.post(
'/match',
[
body('uncategorizedTransactions').exists().isArray({ min: 1 }),
body('uncategorizedTransactions.*').isNumeric().toInt(),
body('matchedTransactions').isArray({ min: 1 }),
body('matchedTransactions.*.reference_type').exists(),
body('matchedTransactions.*.reference_id').isNumeric().toInt(),
],
this.validationResult,
this.matchBankTransaction.bind(this)
);
return router;
}
/**
* Matches the given bank transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
private async matchBankTransaction(
req: Request<{ transactionId: number }>,
res: Response,
next: NextFunction
): Promise<Response | null> {
const { tenantId } = req;
const bodyData = this.matchedBodyData(req);
const uncategorizedTransactions = bodyData?.uncategorizedTransactions;
const matchedTransactions = bodyData?.matchedTransactions;
try {
await this.bankTransactionsMatchingApp.matchTransaction(
tenantId,
uncategorizedTransactions,
matchedTransactions
);
return res.status(200).send({
ids: uncategorizedTransactions,
message: 'The bank transaction has been matched.',
});
} catch (error) {
next(error);
}
}
/**
* Unmatches the matched bank transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
private async unmatchMatchedBankTransaction(
req: Request<{ transactionId: number }>,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { transactionId } = req.params;
try {
await this.bankTransactionsMatchingApp.unmatchMatchedTransaction(
tenantId,
transactionId
);
return res.status(200).send({
id: transactionId,
message: 'The bank matched transaction has been unmatched.',
});
} catch (error) {
next(error);
}
}
}

View File

@@ -2,38 +2,17 @@ import Container, { Inject, Service } from 'typedi';
import { Router } from 'express'; import { Router } from 'express';
import BaseController from '@/api/controllers/BaseController'; import BaseController from '@/api/controllers/BaseController';
import { PlaidBankingController } from './PlaidBankingController'; import { PlaidBankingController } from './PlaidBankingController';
import { BankingRulesController } from './BankingRulesController';
import { BankTransactionsMatchingController } from './BankTransactionsMatchingController';
import { RecognizedTransactionsController } from './RecognizedTransactionsController';
import { BankAccountsController } from './BankAccountsController';
import { BankingUncategorizedController } from './BankingUncategorizedController';
@Service() @Service()
export class BankingController extends BaseController { export class BankingController extends BaseController {
/** /**
* Router constructor. * Router constructor.
*/ */
public router() { router() {
const router = Router(); const router = Router();
router.use('/plaid', Container.get(PlaidBankingController).router()); router.use('/plaid', Container.get(PlaidBankingController).router());
router.use('/rules', Container.get(BankingRulesController).router());
router.use(
'/matches',
Container.get(BankTransactionsMatchingController).router()
);
router.use(
'/recognized',
Container.get(RecognizedTransactionsController).router()
);
router.use(
'/bank_accounts',
Container.get(BankAccountsController).router()
);
router.use(
'/categorize',
Container.get(BankingUncategorizedController).router()
);
return router; return router;
} }
} }

View File

@@ -1,214 +0,0 @@
import { Inject, Service } from 'typedi';
import { NextFunction, Request, Response, Router } from 'express';
import BaseController from '@/api/controllers/BaseController';
import { BankRulesApplication } from '@/services/Banking/Rules/BankRulesApplication';
import { body, param } from 'express-validator';
import {
ICreateBankRuleDTO,
IEditBankRuleDTO,
} from '@/services/Banking/Rules/types';
@Service()
export class BankingRulesController extends BaseController {
@Inject()
private bankRulesApplication: BankRulesApplication;
/**
* Bank rule DTO validation schema.
*/
private get bankRuleValidationSchema() {
return [
body('name').isString().exists(),
body('order').isInt({ min: 0 }),
// Apply to if transaction is.
body('apply_if_account_id')
.isInt({ min: 0 })
.optional({ nullable: true }),
body('apply_if_transaction_type').isIn(['deposit', 'withdrawal']),
// Conditions
body('conditions_type').isString().isIn(['and', 'or']).default('and'),
body('conditions').isArray({ min: 1 }),
body('conditions.*.field').exists().isIn(['description', 'amount']),
body('conditions.*.comparator')
.exists()
.isIn([
'equals',
'equal',
'contains',
'not_contain',
'bigger',
'bigger_or_equal',
'smaller',
'smaller_or_equal',
])
.default('contain')
.trim(),
body('conditions.*.value').exists().trim(),
// Assign
body('assign_category').isString(),
body('assign_account_id').isInt({ min: 0 }),
body('assign_payee').isString().optional({ nullable: true }),
body('assign_memo').isString().optional({ nullable: true }),
];
}
/**
* Router constructor.
*/
public router() {
const router = Router();
router.post(
'/',
[...this.bankRuleValidationSchema],
this.validationResult,
this.createBankRule.bind(this)
);
router.post(
'/:id',
[param('id').toInt().exists(), ...this.bankRuleValidationSchema],
this.validationResult,
this.editBankRule.bind(this)
);
router.delete(
'/:id',
[param('id').toInt().exists()],
this.validationResult,
this.deleteBankRule.bind(this)
);
router.get(
'/:id',
[param('id').toInt().exists()],
this.validationResult,
this.getBankRule.bind(this)
);
router.get(
'/',
[param('id').toInt().exists()],
this.validationResult,
this.getBankRules.bind(this)
);
return router;
}
/**
* Creates a new bank rule.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async createBankRule(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const createBankRuleDTO = this.matchedBodyData(req) as ICreateBankRuleDTO;
try {
const bankRule = await this.bankRulesApplication.createBankRule(
tenantId,
createBankRuleDTO
);
return res.status(200).send({
id: bankRule.id,
message: 'The bank rule has been created successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Edits the given bank rule.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async editBankRule(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: ruleId } = req.params;
const editBankRuleDTO = this.matchedBodyData(req) as IEditBankRuleDTO;
try {
await this.bankRulesApplication.editBankRule(
tenantId,
ruleId,
editBankRuleDTO
);
return res.status(200).send({
id: ruleId,
message: 'The bank rule has been updated successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Deletes the given bank rule.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async deleteBankRule(
req: Request<{ id: number }>,
res: Response,
next: NextFunction
) {
const { id: ruleId } = req.params;
const { tenantId } = req;
try {
await this.bankRulesApplication.deleteBankRule(tenantId, ruleId);
return res
.status(200)
.send({ message: 'The bank rule has been deleted.', id: ruleId });
} catch (error) {
next(error);
}
}
/**
* Retrieve the given bank rule.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async getBankRule(req: Request, res: Response, next: NextFunction) {
const { id: ruleId } = req.params;
const { tenantId } = req;
try {
const bankRule = await this.bankRulesApplication.getBankRule(
tenantId,
ruleId
);
return res.status(200).send({ bankRule });
} catch (error) {
next(error);
}
}
/**
* Retrieves the bank rules.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async getBankRules(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
try {
const bankRules = await this.bankRulesApplication.getBankRules(tenantId);
return res.status(200).send({ bankRules });
} catch (error) {
next(error);
}
}
}

View File

@@ -1,57 +0,0 @@
import { Inject, Service } from 'typedi';
import { NextFunction, Request, Response, Router } from 'express';
import { query } from 'express-validator';
import BaseController from '../BaseController';
import { GetAutofillCategorizeTransaction } from '@/services/Banking/RegonizeTranasctions/GetAutofillCategorizeTransaction';
@Service()
export class BankingUncategorizedController extends BaseController {
@Inject()
private getAutofillCategorizeTransactionService: GetAutofillCategorizeTransaction;
/**
* Router constructor.
*/
router() {
const router = Router();
router.get(
'/autofill',
[
query('uncategorizedTransactionIds').isArray({ min: 1 }),
query('uncategorizedTransactionIds.*').isNumeric().toInt(),
],
this.validationResult,
this.getAutofillCategorizeTransaction.bind(this)
);
return router;
}
/**
* Retrieves the autofill values of the categorize form of the given
* uncategorized transactions.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response | null>}
*/
public async getAutofillCategorizeTransaction(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const uncategorizedTransactionIds = req.query.uncategorizedTransactionIds;
try {
const data =
await this.getAutofillCategorizeTransactionService.getAutofillCategorizeTransaction(
tenantId,
uncategorizedTransactionIds
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
}
}

View File

@@ -1,197 +0,0 @@
import { Inject, Service } from 'typedi';
import { body, param, query } from 'express-validator';
import { NextFunction, Request, Response, Router } from 'express';
import BaseController from '../BaseController';
import { ExcludeBankTransactionsApplication } from '@/services/Banking/Exclude/ExcludeBankTransactionsApplication';
import { map, parseInt, trim } from 'lodash';
@Service()
export class ExcludeBankTransactionsController extends BaseController {
@Inject()
private excludeBankTransactionApp: ExcludeBankTransactionsApplication;
/**
* Router constructor.
*/
public router() {
const router = Router();
router.put(
'/transactions/exclude',
[body('ids').exists()],
this.validationResult,
this.excludeBulkBankTransactions.bind(this)
);
router.put(
'/transactions/unexclude',
[body('ids').exists()],
this.validationResult,
this.unexcludeBulkBankTransactins.bind(this)
);
router.put(
'/transactions/:transactionId/exclude',
[param('transactionId').exists().toInt()],
this.validationResult,
this.excludeBankTransaction.bind(this)
);
router.put(
'/transactions/:transactionId/unexclude',
[param('transactionId').exists()],
this.validationResult,
this.unexcludeBankTransaction.bind(this)
);
router.get(
'/excluded',
[
query('account_id').optional().isNumeric().toInt(),
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
],
this.validationResult,
this.getExcludedBankTransactions.bind(this)
);
return router;
}
/**
* Marks a bank transaction as excluded.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns
*/
private async excludeBankTransaction(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const { tenantId } = req;
const { transactionId } = req.params;
try {
await this.excludeBankTransactionApp.excludeBankTransaction(
tenantId,
transactionId
);
return res.status(200).send({
message: 'The bank transaction has been excluded.',
id: transactionId,
});
} catch (error) {
next(error);
}
}
/**
* Marks a bank transaction as not excluded.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|void>}
*/
private async unexcludeBankTransaction(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const { tenantId } = req;
const { transactionId } = req.params;
try {
await this.excludeBankTransactionApp.unexcludeBankTransaction(
tenantId,
transactionId
);
return res.status(200).send({
message: 'The bank transaction has been unexcluded.',
id: transactionId,
});
} catch (error) {
next(error);
}
}
/**
* Exclude bank transactions in bulk.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async excludeBulkBankTransactions(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { ids } = this.matchedBodyData(req);
try {
await this.excludeBankTransactionApp.excludeBankTransactions(
tenantId,
ids
);
return res.status(200).send({
message: 'The given bank transactions have been excluded',
ids,
});
} catch (error) {
next(error);
}
}
/**
* Unexclude the given bank transactions in bulk.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response | null>}
*/
private async unexcludeBulkBankTransactins(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | null> {
const { tenantId } = req;
const { ids } = this.matchedBodyData(req);
try {
await this.excludeBankTransactionApp.unexcludeBankTransactions(
tenantId,
ids
);
return res.status(200).send({
message: 'The given bank transactions have been excluded',
ids,
});
} catch (error) {
next(error);
}
}
/**
* Retrieves the excluded uncategorized bank transactions.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
private async getExcludedBankTransactions(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const { tenantId } = req;
const filter = this.matchedQueryData(req);
try {
const data =
await this.excludeBankTransactionApp.getExcludedBankTransactions(
tenantId,
filter
);
return res.status(200).send(data);
} catch (error) {
next(error);
}
}
}

View File

@@ -1,87 +0,0 @@
import { Inject, Service } from 'typedi';
import { NextFunction, Request, Response, Router } from 'express';
import { query } from 'express-validator';
import BaseController from '@/api/controllers/BaseController';
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
@Service()
export class RecognizedTransactionsController extends BaseController {
@Inject()
private cashflowApplication: CashflowApplication;
/**
* Router constructor.
*/
router() {
const router = Router();
router.get(
'/',
[
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
query('account_id').optional().isNumeric().toInt(),
],
this.validationResult,
this.getRecognizedTransactions.bind(this)
);
router.get(
'/transactions/:uncategorizedTransactionId',
this.getRecognizedTransaction.bind(this)
);
return router;
}
/**
* Retrieves the recognized bank transactions.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
async getRecognizedTransactions(
req: Request<{ accountId: number }>,
res: Response,
next: NextFunction
) {
const filter = this.matchedQueryData(req);
const { tenantId } = req;
try {
const data = await this.cashflowApplication.getRecognizedTransactions(
tenantId,
filter
);
return res.status(200).send(data);
} catch (error) {
next(error);
}
}
/**
* Retrieves the recognized transaction of the ginen uncategorized transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
async getRecognizedTransaction(
req: Request<{ uncategorizedTransactionId: number }>,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { uncategorizedTransactionId } = req.params;
try {
const data = await this.cashflowApplication.getRecognizedTransaction(
tenantId,
uncategorizedTransactionId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
}
}

View File

@@ -4,7 +4,6 @@ import CommandCashflowTransaction from './NewCashflowTransaction';
import DeleteCashflowTransaction from './DeleteCashflowTransaction'; import DeleteCashflowTransaction from './DeleteCashflowTransaction';
import GetCashflowTransaction from './GetCashflowTransaction'; import GetCashflowTransaction from './GetCashflowTransaction';
import GetCashflowAccounts from './GetCashflowAccounts'; import GetCashflowAccounts from './GetCashflowAccounts';
import { ExcludeBankTransactionsController } from '../Banking/ExcludeBankTransactionsController';
@Service() @Service()
export default class CashflowController { export default class CashflowController {
@@ -15,7 +14,6 @@ export default class CashflowController {
const router = Router(); const router = Router();
router.use(Container.get(CommandCashflowTransaction).router()); router.use(Container.get(CommandCashflowTransaction).router());
router.use(Container.get(ExcludeBankTransactionsController).router());
router.use(Container.get(GetCashflowTransaction).router()); router.use(Container.get(GetCashflowTransaction).router());
router.use(Container.get(GetCashflowAccounts).router()); router.use(Container.get(GetCashflowAccounts).router());
router.use(Container.get(DeleteCashflowTransaction).router()); router.use(Container.get(DeleteCashflowTransaction).router());

View File

@@ -31,6 +31,7 @@ export default class GetCashflowAccounts extends BaseController {
query('search_keyword').optional({ nullable: true }).isString().trim(), query('search_keyword').optional({ nullable: true }).isString().trim(),
], ],
this.asyncMiddleware(this.getCashflowAccounts), this.asyncMiddleware(this.getCashflowAccounts),
this.catchServiceErrors
); );
return router; return router;
} }
@@ -66,4 +67,22 @@ export default class GetCashflowAccounts extends BaseController {
next(error); next(error);
} }
}; };
/**
* Catches the service errors.
* @param {Error} error - Error.
* @param {Request} req - Request.
* @param {Response} res - Response.
* @param {NextFunction} next -
*/
private catchServiceErrors(
error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof ServiceError) {
}
next(error);
}
} }

View File

@@ -1,37 +1,23 @@
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { param, query } from 'express-validator'; import { param } from 'express-validator';
import BaseController from '../BaseController'; import BaseController from '../BaseController';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { AbilitySubject, CashflowAction } from '@/interfaces'; import { AbilitySubject, CashflowAction } from '@/interfaces';
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication'; import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
import { GetMatchedTransactionsFilter } from '@/services/Banking/Matching/types';
import { MatchBankTransactionsApplication } from '@/services/Banking/Matching/MatchBankTransactionsApplication';
@Service() @Service()
export default class GetCashflowAccounts extends BaseController { export default class GetCashflowAccounts extends BaseController {
@Inject() @Inject()
private cashflowApplication: CashflowApplication; private cashflowApplication: CashflowApplication;
@Inject()
private bankTransactionsMatchingApp: MatchBankTransactionsApplication;
/** /**
* Controller router. * Controller router.
*/ */
public router() { public router() {
const router = Router(); const router = Router();
router.get(
'/transactions/matches',
[
query('uncategorizeTransactionsIds').exists().isArray({ min: 1 }),
query('uncategorizeTransactionsIds.*').exists().isNumeric().toInt(),
],
this.validationResult,
this.getMatchedTransactions.bind(this)
);
router.get( router.get(
'/transactions/:transactionId', '/transactions/:transactionId',
CheckPolicies(CashflowAction.View, AbilitySubject.Cashflow), CheckPolicies(CashflowAction.View, AbilitySubject.Cashflow),
@@ -49,7 +35,7 @@ export default class GetCashflowAccounts extends BaseController {
* @param {NextFunction} next * @param {NextFunction} next
*/ */
private getCashflowTransaction = async ( private getCashflowTransaction = async (
req: Request<{ transactionId: number }>, req: Request,
res: Response, res: Response,
next: NextFunction next: NextFunction
) => { ) => {
@@ -61,6 +47,7 @@ export default class GetCashflowAccounts extends BaseController {
tenantId, tenantId,
transactionId transactionId
); );
return res.status(200).send({ return res.status(200).send({
cashflow_transaction: this.transfromToResponse(cashflowTransaction), cashflow_transaction: this.transfromToResponse(cashflowTransaction),
}); });
@@ -69,39 +56,6 @@ export default class GetCashflowAccounts extends BaseController {
} }
}; };
/**
* Retrieves the matched transactions.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async getMatchedTransactions(
req: Request<
{ transactionId: number },
null,
null,
{ uncategorizeTransactionsIds: Array<number> }
>,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const uncategorizeTransactionsIds = req.query.uncategorizeTransactionsIds;
const filter = this.matchedQueryData(req) as GetMatchedTransactionsFilter;
try {
const data =
await this.bankTransactionsMatchingApp.getMatchedTransactions(
tenantId,
uncategorizeTransactionsIds,
filter
);
return res.status(200).send(data);
} catch (error) {
next(error);
}
}
/** /**
* Catches the service errors. * Catches the service errors.
* @param {Error} error - Error. * @param {Error} error - Error.

View File

@@ -1,15 +1,10 @@
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { ValidationChain, body, check, param, query } from 'express-validator'; import { ValidationChain, check, param, query } from 'express-validator';
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { omit } from 'lodash';
import BaseController from '../BaseController'; import BaseController from '../BaseController';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { import { AbilitySubject, CashflowAction } from '@/interfaces';
AbilitySubject,
CashflowAction,
ICategorizeCashflowTransactioDTO,
} from '@/interfaces';
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication'; import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
@Service() @Service()
@@ -43,23 +38,13 @@ export default class NewCashflowTransactionController extends BaseController {
this.asyncMiddleware(this.newCashflowTransaction), this.asyncMiddleware(this.newCashflowTransaction),
this.catchServiceErrors this.catchServiceErrors
); );
router.post(
'/transactions/uncategorize/bulk',
[
body('ids').isArray({ min: 1 }),
body('ids.*').exists().isNumeric().toInt(),
],
this.validationResult,
this.uncategorizeBulkTransactions.bind(this),
this.catchServiceErrors
);
router.post( router.post(
'/transactions/:id/uncategorize', '/transactions/:id/uncategorize',
this.revertCategorizedCashflowTransaction, this.revertCategorizedCashflowTransaction,
this.catchServiceErrors this.catchServiceErrors
); );
router.post( router.post(
'/transactions/categorize', '/transactions/:id/categorize',
this.categorizeCashflowTransactionValidationSchema, this.categorizeCashflowTransactionValidationSchema,
this.validationResult, this.validationResult,
this.categorizeCashflowTransaction, this.categorizeCashflowTransaction,
@@ -104,7 +89,6 @@ export default class NewCashflowTransactionController extends BaseController {
*/ */
public get categorizeCashflowTransactionValidationSchema() { public get categorizeCashflowTransactionValidationSchema() {
return [ return [
check('uncategorized_transaction_ids').exists().isArray({ min: 1 }),
check('date').exists().isISO8601().toDate(), check('date').exists().isISO8601().toDate(),
check('credit_account_id').exists().isInt().toInt(), check('credit_account_id').exists().isInt().toInt(),
check('transaction_number').optional(), check('transaction_number').optional(),
@@ -122,11 +106,12 @@ export default class NewCashflowTransactionController extends BaseController {
public get newTransactionValidationSchema() { public get newTransactionValidationSchema() {
return [ return [
check('date').exists().isISO8601().toDate(), check('date').exists().isISO8601().toDate(),
check('reference_no').optional({ nullable: true }).trim(), check('reference_no').optional({ nullable: true }).trim().escape(),
check('description') check('description')
.optional({ nullable: true }) .optional({ nullable: true })
.isLength({ min: 3 }) .isLength({ min: 3 })
.trim(), .trim()
.escape(),
check('transaction_type').exists(), check('transaction_type').exists(),
check('amount').exists().isFloat().toFloat(), check('amount').exists().isFloat().toFloat(),
@@ -176,7 +161,7 @@ export default class NewCashflowTransactionController extends BaseController {
* @param {NextFunction} next * @param {NextFunction} next
*/ */
private revertCategorizedCashflowTransaction = async ( private revertCategorizedCashflowTransaction = async (
req: Request<{ id: number }>, req: Request,
res: Response, res: Response,
next: NextFunction next: NextFunction
) => { ) => {
@@ -194,34 +179,6 @@ export default class NewCashflowTransactionController extends BaseController {
} }
}; };
/**
* Uncategorize the given transactions in bulk.
* @param {Request<{}>} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response | null>}
*/
private uncategorizeBulkTransactions = async (
req: Request<{}>,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { ids: uncategorizedTransactionIds } = this.matchedBodyData(req);
try {
await this.cashflowApplication.uncategorizeTransactions(
tenantId,
uncategorizedTransactionIds
);
return res.status(200).send({
message: 'The given transactions have been uncategorized successfully.',
});
} catch (error) {
next(error);
}
};
/** /**
* Categorize the cashflow transaction. * Categorize the cashflow transaction.
* @param {Request} req * @param {Request} req
@@ -234,19 +191,14 @@ export default class NewCashflowTransactionController extends BaseController {
next: NextFunction next: NextFunction
) => { ) => {
const { tenantId } = req; const { tenantId } = req;
const matchedObject = this.matchedBodyData(req); const { id: cashflowTransactionId } = req.params;
const categorizeDTO = omit(matchedObject, [ const cashflowTransaction = this.matchedBodyData(req);
'uncategorizedTransactionIds',
]) as ICategorizeCashflowTransactioDTO;
const uncategorizedTransactionIds =
matchedObject.uncategorizedTransactionIds;
try { try {
await this.cashflowApplication.categorizeTransaction( await this.cashflowApplication.categorizeTransaction(
tenantId, tenantId,
uncategorizedTransactionIds, cashflowTransactionId,
categorizeDTO cashflowTransaction
); );
return res.status(200).send({ return res.status(200).send({
message: 'The cashflow transaction has been created successfully.', message: 'The cashflow transaction has been created successfully.',
@@ -317,7 +269,7 @@ export default class NewCashflowTransactionController extends BaseController {
* @param {NextFunction} next * @param {NextFunction} next
*/ */
public getUncategorizedCashflowTransactions = async ( public getUncategorizedCashflowTransactions = async (
req: Request<{ id: number }>, req: Request,
res: Response, res: Response,
next: NextFunction next: NextFunction
) => { ) => {

View File

@@ -56,7 +56,7 @@ export default class ContactsController extends BaseController {
*/ */
get autocompleteQuerySchema() { get autocompleteQuerySchema() {
return [ return [
query('column_sort_by').optional().trim(), query('column_sort_by').optional().trim().escape(),
query('sort_order').optional().isIn(['desc', 'asc']), query('sort_order').optional().isIn(['desc', 'asc']),
query('stringified_filter_roles').optional().isJSON(), query('stringified_filter_roles').optional().isJSON(),
@@ -122,27 +122,32 @@ export default class ContactsController extends BaseController {
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('first_name') check('first_name')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('last_name') check('last_name')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('company_name') check('company_name')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('display_name') check('display_name')
.exists() .exists()
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('email') check('email')
@@ -160,101 +165,120 @@ export default class ContactsController extends BaseController {
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('personal_phone') check('personal_phone')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('billing_address_1') check('billing_address_1')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('billing_address_2') check('billing_address_2')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('billing_address_city') check('billing_address_city')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('billing_address_country') check('billing_address_country')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('billing_address_email') check('billing_address_email')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.isEmail() .isEmail()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('billing_address_postcode') check('billing_address_postcode')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('billing_address_phone') check('billing_address_phone')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('billing_address_state') check('billing_address_state')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('shipping_address_1') check('shipping_address_1')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('shipping_address_2') check('shipping_address_2')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('shipping_address_city') check('shipping_address_city')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('shipping_address_country') check('shipping_address_country')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('shipping_address_email') check('shipping_address_email')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.isEmail() .isEmail()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('shipping_address_postcode') check('shipping_address_postcode')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('shipping_address_phone') check('shipping_address_phone')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('shipping_address_state') check('shipping_address_state')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('note') check('note')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.TEXT }), .isLength({ max: DATATYPES_LENGTH.TEXT }),
check('active').optional().isBoolean().toBoolean(), check('active').optional().isBoolean().toBoolean(),
]; ];

View File

@@ -106,7 +106,11 @@ export default class CustomersController extends ContactsController {
*/ */
get customerDTOSchema() { get customerDTOSchema() {
return [ return [
check('customer_type').exists().isIn(['business', 'individual']).trim(), check('customer_type')
.exists()
.isIn(['business', 'individual'])
.trim()
.escape(),
]; ];
} }
@@ -119,6 +123,7 @@ export default class CustomersController extends ContactsController {
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: 3 }), .isLength({ max: 3 }),
]; ];
} }
@@ -128,7 +133,7 @@ export default class CustomersController extends ContactsController {
*/ */
get validateListQuerySchema() { get validateListQuerySchema() {
return [ return [
query('column_sort_by').optional().trim(), query('column_sort_by').optional().trim().escape(),
query('sort_order').optional().isIn(['desc', 'asc']), query('sort_order').optional().isIn(['desc', 'asc']),
query('page').optional().isNumeric().toInt(), query('page').optional().isNumeric().toInt(),

View File

@@ -106,6 +106,7 @@ export default class VendorsController extends ContactsController {
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ min: 3, max: 3 }), .isLength({ min: 3, max: 3 }),
]; ];
} }

View File

@@ -67,7 +67,7 @@ export default class CurrenciesController extends BaseController {
} }
get currencyParamSchema(): ValidationChain[] { get currencyParamSchema(): ValidationChain[] {
return [param('currency_code').exists().trim()]; return [param('currency_code').exists().trim().escape()];
} }
get listSchema(): ValidationChain[] { get listSchema(): ValidationChain[] {
@@ -187,13 +187,11 @@ export default class CurrenciesController extends BaseController {
} }
if (error.errorType === 'currency_code_exists') { if (error.errorType === 'currency_code_exists') {
return res.boom.badRequest(null, { return res.boom.badRequest(null, {
errors: [ errors: [{
{ type: 'CURRENCY_CODE_EXISTS',
type: 'CURRENCY_CODE_EXISTS', message: 'The given currency code is already exists.',
message: 'The given currency code is already exists.', code: 200,
code: 200, }],
},
],
}); });
} }
if (error.errorType === 'CANNOT_DELETE_BASE_CURRENCY') { if (error.errorType === 'CANNOT_DELETE_BASE_CURRENCY') {

View File

@@ -89,6 +89,7 @@ export class ExpensesController extends BaseController {
check('reference_no') check('reference_no')
.optional({ nullable: true }) .optional({ nullable: true })
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('payment_date').exists().isISO8601().toDate(), check('payment_date').exists().isISO8601().toDate(),
check('payment_account_id') check('payment_account_id')
@@ -122,6 +123,7 @@ export class ExpensesController extends BaseController {
check('categories.*.description') check('categories.*.description')
.optional() .optional()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('categories.*.landed_cost').optional().isBoolean().toBoolean(), check('categories.*.landed_cost').optional().isBoolean().toBoolean(),
check('categories.*.project_id') check('categories.*.project_id')
@@ -142,6 +144,7 @@ export class ExpensesController extends BaseController {
check('reference_no') check('reference_no')
.optional({ nullable: true }) .optional({ nullable: true })
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('payment_date').exists().isISO8601().toDate(), check('payment_date').exists().isISO8601().toDate(),
check('payment_account_id') check('payment_account_id')
@@ -176,6 +179,7 @@ export class ExpensesController extends BaseController {
check('categories.*.description') check('categories.*.description')
.optional() .optional()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('categories.*.landed_cost').optional().isBoolean().toBoolean(), check('categories.*.landed_cost').optional().isBoolean().toBoolean(),
check('categories.*.project_id') check('categories.*.project_id')

View File

@@ -1,7 +1,9 @@
import { query } from 'express-validator'; import { query } from 'express-validator';
import BaseController from '../BaseController'; import BaseController from "../BaseController";
export default class BaseFinancialReportController extends BaseController { export default class BaseFinancialReportController extends BaseController {
get sheetNumberFormatValidationSchema() { get sheetNumberFormatValidationSchema() {
return [ return [
query('number_format.precision') query('number_format.precision')
@@ -17,7 +19,8 @@ export default class BaseFinancialReportController extends BaseController {
query('number_format.negative_format') query('number_format.negative_format')
.optional() .optional()
.isIn(['parentheses', 'mines']) .isIn(['parentheses', 'mines'])
.trim(), .trim()
.escape(),
]; ];
} }
} }

View File

@@ -51,7 +51,8 @@ export default class InventoryDetailsController extends BaseController {
query('number_format.negative_format') query('number_format.negative_format')
.optional() .optional()
.isIn(['parentheses', 'mines']) .isIn(['parentheses', 'mines'])
.trim(), .trim()
.escape(),
query('from_date').optional(), query('from_date').optional(),
query('to_date').optional(), query('to_date').optional(),

View File

@@ -36,7 +36,7 @@ export default class JournalSheetController extends BaseFinancialReportControlle
return [ return [
query('from_date').optional().isISO8601(), query('from_date').optional().isISO8601(),
query('to_date').optional().isISO8601(), query('to_date').optional().isISO8601(),
query('transaction_type').optional().trim(), query('transaction_type').optional().trim().escape(),
query('transaction_id').optional().isInt().toInt(), query('transaction_id').optional().isInt().toInt(),
oneOf( oneOf(
[ [

View File

@@ -40,7 +40,8 @@ export default class TransactionsByReferenceController extends BaseController {
query('number_format.negative_format') query('number_format.negative_format')
.optional() .optional()
.isIn(['parentheses', 'mines']) .isIn(['parentheses', 'mines'])
.trim(), .trim()
.escape(),
]; ];
} }

View File

@@ -86,7 +86,7 @@ export default class InventoryAdjustmentsController extends BaseController {
*/ */
get validateListQuerySchema() { get validateListQuerySchema() {
return [ return [
query('column_sort_by').optional().trim(), query('column_sort_by').optional().trim().escape(),
query('sort_order').optional().isIn(['desc', 'asc']), query('sort_order').optional().isIn(['desc', 'asc']),
query('page').optional().isNumeric().toInt(), query('page').optional().isNumeric().toInt(),

View File

@@ -25,7 +25,7 @@ export default class InviteUsersController extends BaseController {
router.post( router.post(
'/send', '/send',
[ [
body('email').exists().trim(), body('email').exists().trim().escape(),
body('role_id').exists().isNumeric().toInt(), body('role_id').exists().isNumeric().toInt(),
], ],
this.validationResult, this.validationResult,
@@ -57,7 +57,7 @@ export default class InviteUsersController extends BaseController {
); );
router.get( router.get(
'/invited/:token', '/invited/:token',
[param('token').exists().trim()], [param('token').exists().trim().escape()],
this.validationResult, this.validationResult,
asyncMiddleware(this.invited.bind(this)), asyncMiddleware(this.invited.bind(this)),
this.handleServicesError this.handleServicesError
@@ -72,10 +72,10 @@ export default class InviteUsersController extends BaseController {
*/ */
private get inviteUserDTO() { private get inviteUserDTO() {
return [ return [
check('first_name').exists().trim(), check('first_name').exists().trim().escape(),
check('last_name').exists().trim(), check('last_name').exists().trim().escape(),
check('password').exists().trim().isLength({ min: 5 }), check('password').exists().trim().escape().isLength({ min: 5 }),
param('token').exists().trim(), param('token').exists().trim().escape(),
]; ];
} }

View File

@@ -73,11 +73,13 @@ export default class ItemsCategoriesController extends BaseController {
check('name') check('name')
.exists() .exists()
.trim() .trim()
.escape()
.isLength({ min: 0, max: DATATYPES_LENGTH.STRING }), .isLength({ min: 0, max: DATATYPES_LENGTH.STRING }),
check('description') check('description')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.TEXT }), .isLength({ max: DATATYPES_LENGTH.TEXT }),
check('sell_account_id') check('sell_account_id')
.optional({ nullable: true }) .optional({ nullable: true })
@@ -99,8 +101,9 @@ export default class ItemsCategoriesController extends BaseController {
*/ */
get categoriesListValidationSchema() { get categoriesListValidationSchema() {
return [ return [
query('column_sort_by').optional().trim(), query('column_sort_by').optional().trim().escape(),
query('sort_order').optional().trim().isIn(['desc', 'asc']), query('sort_order').optional().trim().escape().isIn(['desc', 'asc']),
query('stringified_filter_roles').optional().isJSON(), query('stringified_filter_roles').optional().isJSON(),
]; ];
} }
@@ -204,12 +207,14 @@ export default class ItemsCategoriesController extends BaseController {
}; };
try { try {
const { itemCategories, filterMeta } = const {
await this.itemCategoriesService.getItemCategoriesList( itemCategories,
tenantId, filterMeta,
itemCategoriesFilter, } = await this.itemCategoriesService.getItemCategoriesList(
user tenantId,
); itemCategoriesFilter,
user
);
return res.status(200).send({ return res.status(200).send({
item_categories: itemCategories, item_categories: itemCategories,
filter_meta: this.transfromToResponse(filterMeta), filter_meta: this.transfromToResponse(filterMeta),

View File

@@ -96,11 +96,13 @@ export default class ItemsController extends BaseController {
.exists() .exists()
.isString() .isString()
.trim() .trim()
.escape()
.isIn(['service', 'non-inventory', 'inventory']), .isIn(['service', 'non-inventory', 'inventory']),
check('code') check('code')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
// Purchase attributes. // Purchase attributes.
check('purchasable').optional().isBoolean().toBoolean(), check('purchasable').optional().isBoolean().toBoolean(),
@@ -139,11 +141,13 @@ export default class ItemsController extends BaseController {
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.TEXT }), .isLength({ max: DATATYPES_LENGTH.TEXT }),
check('purchase_description') check('purchase_description')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.TEXT }), .isLength({ max: DATATYPES_LENGTH.TEXT }),
check('sell_tax_rate_id').optional({ nullable: true }).isInt().toInt(), check('sell_tax_rate_id').optional({ nullable: true }).isInt().toInt(),
check('purchase_tax_rate_id') check('purchase_tax_rate_id')
@@ -158,6 +162,7 @@ export default class ItemsController extends BaseController {
.optional() .optional()
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.TEXT }), .isLength({ max: DATATYPES_LENGTH.TEXT }),
check('active').optional().isBoolean().toBoolean(), check('active').optional().isBoolean().toBoolean(),
@@ -179,7 +184,7 @@ export default class ItemsController extends BaseController {
*/ */
private get validateListQuerySchema() { private get validateListQuerySchema() {
return [ return [
query('column_sort_by').optional().trim(), query('column_sort_by').optional().trim().escape(),
query('sort_order').optional().isIn(['desc', 'asc']), query('sort_order').optional().isIn(['desc', 'asc']),
query('page').optional().isNumeric().toInt(), query('page').optional().isNumeric().toInt(),

View File

@@ -94,21 +94,25 @@ export default class ManualJournalsController extends BaseController {
.optional() .optional()
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('journal_type') check('journal_type')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('reference') check('reference')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('description') check('description')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.trim() .trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.TEXT }), .isLength({ max: DATATYPES_LENGTH.TEXT }),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(), check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('publish').optional().isBoolean().toBoolean(), check('publish').optional().isBoolean().toBoolean(),
@@ -159,7 +163,7 @@ export default class ManualJournalsController extends BaseController {
query('page_size').optional().isNumeric().toInt(), query('page_size').optional().isNumeric().toInt(),
query('custom_view_id').optional().isNumeric().toInt(), query('custom_view_id').optional().isNumeric().toInt(),
query('column_sort_by').optional().trim(), query('column_sort_by').optional().trim().escape(),
query('sort_order').optional().isIn(['desc', 'asc']), query('sort_order').optional().isIn(['desc', 'asc']),
query('stringified_filter_roles').optional().isJSON(), query('stringified_filter_roles').optional().isJSON(),

View File

@@ -61,14 +61,15 @@ export default class MediaController extends BaseController {
get uploadValidationSchema() { get uploadValidationSchema() {
return [ return [
check('model_name').optional().trim(), // check('attachment'),
check('model_id').optional().isNumeric(), check('model_name').optional().trim().escape(),
check('model_id').optional().isNumeric().toInt(),
]; ];
} }
get linkValidationSchema() { get linkValidationSchema() {
return [ return [
check('model_name').exists().trim(), check('model_name').exists().trim().escape(),
check('model_id').exists().isNumeric().toInt(), check('model_id').exists().isNumeric().toInt(),
] ]
} }

View File

@@ -62,7 +62,7 @@ export default class OrganizationController extends BaseController {
private get commonOrganizationValidationSchema(): ValidationChain[] { private get commonOrganizationValidationSchema(): ValidationChain[] {
return [ return [
check('name').exists().trim(), check('name').exists().trim(),
check('industry').optional({ nullable: true }).isString().trim(), check('industry').optional({ nullable: true }).isString().trim().escape(),
check('location').exists().isString().isISO31661Alpha2(), check('location').exists().isString().isISO31661Alpha2(),
check('base_currency').exists().isISO4217(), check('base_currency').exists().isISO4217(),
check('timezone').exists().isIn(moment.tz.names()), check('timezone').exists().isIn(moment.tz.names()),
@@ -87,7 +87,11 @@ export default class OrganizationController extends BaseController {
private get updateOrganizationValidationSchema(): ValidationChain[] { private get updateOrganizationValidationSchema(): ValidationChain[] {
return [ return [
...this.commonOrganizationValidationSchema, ...this.commonOrganizationValidationSchema,
check('tax_number').optional({ nullable: true }).isString().trim(), check('tax_number')
.optional({ nullable: true })
.isString()
.trim()
.escape(),
]; ];
} }

View File

@@ -100,8 +100,8 @@ export default class BillsController extends BaseController {
*/ */
private get billValidationSchema() { private get billValidationSchema() {
return [ return [
check('bill_number').exists().trim(), check('bill_number').exists().trim().escape(),
check('reference_no').optional().trim(), check('reference_no').optional().trim().escape(),
check('bill_date').exists().isISO8601(), check('bill_date').exists().isISO8601(),
check('due_date').optional().isISO8601(), check('due_date').optional().isISO8601(),
@@ -112,7 +112,7 @@ export default class BillsController extends BaseController {
check('branch_id').optional({ nullable: true }).isNumeric().toInt(), check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('project_id').optional({ nullable: true }).isNumeric().toInt(), check('project_id').optional({ nullable: true }).isNumeric().toInt(),
check('note').optional().trim(), check('note').optional().trim().escape(),
check('open').default(false).isBoolean().toBoolean(), check('open').default(false).isBoolean().toBoolean(),
check('is_inclusive_tax').default(false).isBoolean().toBoolean(), check('is_inclusive_tax').default(false).isBoolean().toBoolean(),
@@ -126,7 +126,10 @@ export default class BillsController extends BaseController {
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
.toFloat(), .toFloat(),
check('entries.*.description').optional({ nullable: true }).trim(), check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.landed_cost') check('entries.*.landed_cost')
.optional({ nullable: true }) .optional({ nullable: true })
.isBoolean() .isBoolean()
@@ -138,6 +141,7 @@ export default class BillsController extends BaseController {
check('entries.*.tax_code') check('entries.*.tax_code')
.optional({ nullable: true }) .optional({ nullable: true })
.trim() .trim()
.escape()
.isString(), .isString(),
check('entries.*.tax_rate_id') check('entries.*.tax_rate_id')
.optional({ nullable: true }) .optional({ nullable: true })
@@ -154,8 +158,8 @@ export default class BillsController extends BaseController {
*/ */
private get billEditValidationSchema() { private get billEditValidationSchema() {
return [ return [
check('bill_number').optional().trim(), check('bill_number').optional().trim().escape(),
check('reference_no').optional().trim(), check('reference_no').optional().trim().escape(),
check('bill_date').exists().isISO8601(), check('bill_date').exists().isISO8601(),
check('due_date').optional().isISO8601(), check('due_date').optional().isISO8601(),
@@ -166,7 +170,7 @@ export default class BillsController extends BaseController {
check('branch_id').optional({ nullable: true }).isNumeric().toInt(), check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('project_id').optional({ nullable: true }).isNumeric().toInt(), check('project_id').optional({ nullable: true }).isNumeric().toInt(),
check('note').optional().trim(), check('note').optional().trim().escape(),
check('open').default(false).isBoolean().toBoolean(), check('open').default(false).isBoolean().toBoolean(),
check('entries').isArray({ min: 1 }), check('entries').isArray({ min: 1 }),
@@ -180,7 +184,10 @@ export default class BillsController extends BaseController {
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
.toFloat(), .toFloat(),
check('entries.*.description').optional({ nullable: true }).trim(), check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.landed_cost') check('entries.*.landed_cost')
.optional({ nullable: true }) .optional({ nullable: true })
.isBoolean() .isBoolean()
@@ -215,8 +222,8 @@ export default class BillsController extends BaseController {
private get dueBillsListingValidationSchema() { private get dueBillsListingValidationSchema() {
return [ return [
query('vendor_id').optional().trim(), query('vendor_id').optional().trim().escape(),
query('payment_made_id').optional().trim(), query('payment_made_id').optional().trim().escape(),
]; ];
} }

View File

@@ -111,15 +111,14 @@ export default class BillsPayments extends BaseController {
check('vendor_id').exists().isNumeric().toInt(), check('vendor_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(), check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('amount').exists().isNumeric().toFloat(),
check('payment_account_id').exists().isNumeric().toInt(), check('payment_account_id').exists().isNumeric().toInt(),
check('payment_number').optional({ nullable: true }).trim(), check('payment_number').optional({ nullable: true }).trim().escape(),
check('payment_date').exists(), check('payment_date').exists(),
check('statement').optional().trim(), check('statement').optional().trim().escape(),
check('reference').optional().trim(), check('reference').optional().trim().escape(),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(), check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('entries').exists().isArray(), check('entries').exists().isArray({ min: 1 }),
check('entries.*.index').optional().isNumeric().toInt(), check('entries.*.index').optional().isNumeric().toInt(),
check('entries.*.bill_id').exists().isNumeric().toInt(), check('entries.*.bill_id').exists().isNumeric().toInt(),
check('entries.*.payment_amount').exists().isNumeric().toFloat(), check('entries.*.payment_amount').exists().isNumeric().toFloat(),

View File

@@ -156,10 +156,13 @@ export default class VendorCreditController extends BaseController {
check('vendor_id').exists().isNumeric().toInt(), check('vendor_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(), check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('vendor_credit_number').optional({ nullable: true }).trim(), check('vendor_credit_number')
check('reference_no').optional().trim(), .optional({ nullable: true })
.trim()
.escape(),
check('reference_no').optional().trim().escape(),
check('vendor_credit_date').exists().isISO8601().toDate(), check('vendor_credit_date').exists().isISO8601().toDate(),
check('note').optional().trim(), check('note').optional().trim().escape(),
check('open').default(false).isBoolean().toBoolean(), check('open').default(false).isBoolean().toBoolean(),
check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(), check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(),
@@ -175,7 +178,10 @@ export default class VendorCreditController extends BaseController {
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
.toFloat(), .toFloat(),
check('entries.*.description').optional({ nullable: true }).trim(), check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.warehouse_id') check('entries.*.warehouse_id')
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
@@ -196,10 +202,13 @@ export default class VendorCreditController extends BaseController {
check('vendor_id').exists().isNumeric().toInt(), check('vendor_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(), check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('vendor_credit_number').optional({ nullable: true }).trim(), check('vendor_credit_number')
check('reference_no').optional().trim(), .optional({ nullable: true })
.trim()
.escape(),
check('reference_no').optional().trim().escape(),
check('vendor_credit_date').exists().isISO8601().toDate(), check('vendor_credit_date').exists().isISO8601().toDate(),
check('note').optional().trim(), check('note').optional().trim().escape(),
check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(), check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(), check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
@@ -214,7 +223,10 @@ export default class VendorCreditController extends BaseController {
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
.toFloat(), .toFloat(),
check('entries.*.description').optional({ nullable: true }).trim(), check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.warehouse_id') check('entries.*.warehouse_id')
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()

View File

@@ -18,7 +18,9 @@ export default class ResourceController extends BaseController {
router.get( router.get(
'/:resource_model/meta', '/:resource_model/meta',
[param('resource_model').exists().trim()], [
param('resource_model').exists().trim().escape()
],
this.asyncMiddleware(this.resourceMeta.bind(this)), this.asyncMiddleware(this.resourceMeta.bind(this)),
this.handleServiceErrors this.handleServiceErrors
); );
@@ -46,7 +48,9 @@ export default class ResourceController extends BaseController {
resourceModel resourceModel
); );
return res.status(200).send({ return res.status(200).send({
resource_meta: this.transfromToResponse(resourceMeta), resource_meta: this.transfromToResponse(
resourceMeta,
),
}); });
} catch (error) { } catch (error) {
next(error); next(error);

View File

@@ -210,9 +210,9 @@ export default class PaymentReceivesController extends BaseController {
check('credit_note_date').exists().isISO8601().toDate(), check('credit_note_date').exists().isISO8601().toDate(),
check('reference_no').optional(), check('reference_no').optional(),
check('credit_note_number').optional({ nullable: true }).trim(), check('credit_note_number').optional({ nullable: true }).trim().escape(),
check('note').optional().trim(), check('note').optional().trim().escape(),
check('terms_conditions').optional().trim(), check('terms_conditions').optional().trim().escape(),
check('open').default(false).isBoolean().toBoolean(), check('open').default(false).isBoolean().toBoolean(),
check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(), check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(),
@@ -228,7 +228,10 @@ export default class PaymentReceivesController extends BaseController {
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
.toFloat(), .toFloat(),
check('entries.*.description').optional({ nullable: true }).trim(), check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.warehouse_id') check('entries.*.warehouse_id')
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()

View File

@@ -9,9 +9,9 @@ import {
} from '@/interfaces'; } from '@/interfaces';
import BaseController from '@/api/controllers/BaseController'; import BaseController from '@/api/controllers/BaseController';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import PaymentsReceivedPages from '@/services/Sales/PaymentReceived/PaymentsReceivedPages'; import PaymentReceivesPages from '@/services/Sales/PaymentReceives/PaymentReceivesPages';
import { PaymentReceivesApplication } from '@/services/Sales/PaymentReceived/PaymentReceivedApplication';
import DynamicListingService from '@/services/DynamicListing/DynamicListService'; import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { PaymentReceivesApplication } from '@/services/Sales/PaymentReceives/PaymentReceivesApplication';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import { ACCEPT_TYPE } from '@/interfaces/Http'; import { ACCEPT_TYPE } from '@/interfaces/Http';
@@ -22,7 +22,7 @@ export default class PaymentReceivesController extends BaseController {
private paymentReceiveApplication: PaymentReceivesApplication; private paymentReceiveApplication: PaymentReceivesApplication;
@Inject() @Inject()
private PaymentsReceivedPages: PaymentsReceivedPages; private PaymentReceivesPages: PaymentReceivesPages;
@Inject() @Inject()
private dynamicListService: DynamicListingService; private dynamicListService: DynamicListingService;
@@ -150,16 +150,16 @@ export default class PaymentReceivesController extends BaseController {
check('customer_id').exists().isNumeric().toInt(), check('customer_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(), check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('amount').exists().isNumeric().toFloat(),
check('payment_date').exists(), check('payment_date').exists(),
check('reference_no').optional(), check('reference_no').optional(),
check('deposit_account_id').exists().isNumeric().toInt(), check('deposit_account_id').exists().isNumeric().toInt(),
check('payment_receive_no').optional({ nullable: true }).trim(), check('payment_receive_no').optional({ nullable: true }).trim().escape(),
check('statement').optional().trim(), check('statement').optional().trim().escape(),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(), check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('entries').isArray({}), check('entries').isArray({ min: 1 }),
check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(), check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(),
check('entries.*.index').optional().isNumeric().toInt(), check('entries.*.index').optional().isNumeric().toInt(),
check('entries.*.invoice_id').exists().isNumeric().toInt(), check('entries.*.invoice_id').exists().isNumeric().toInt(),
@@ -176,6 +176,7 @@ export default class PaymentReceivesController extends BaseController {
private get validatePaymentReceiveList(): ValidationChain[] { private get validatePaymentReceiveList(): ValidationChain[] {
return [ return [
query('stringified_filter_roles').optional().isJSON(), query('stringified_filter_roles').optional().isJSON(),
query('view_slug').optional({ nullable: true }).isString().trim(), query('view_slug').optional({ nullable: true }).isString().trim(),
query('column_sort_by').optional(), query('column_sort_by').optional(),
@@ -229,7 +230,7 @@ export default class PaymentReceivesController extends BaseController {
try { try {
const storedPaymentReceive = const storedPaymentReceive =
await this.paymentReceiveApplication.createPaymentReceived( await this.paymentReceiveApplication.createPaymentReceive(
tenantId, tenantId,
paymentReceive, paymentReceive,
user user
@@ -376,7 +377,7 @@ export default class PaymentReceivesController extends BaseController {
const { customerId } = this.matchedQueryData(req); const { customerId } = this.matchedQueryData(req);
try { try {
const entries = await this.PaymentsReceivedPages.getNewPageEntries( const entries = await this.PaymentReceivesPages.getNewPageEntries(
tenantId, tenantId,
customerId customerId
); );
@@ -404,7 +405,7 @@ export default class PaymentReceivesController extends BaseController {
try { try {
const { paymentReceive, entries } = const { paymentReceive, entries } =
await this.PaymentsReceivedPages.getPaymentReceiveEditPage( await this.PaymentReceivesPages.getPaymentReceiveEditPage(
tenantId, tenantId,
paymentReceiveId, paymentReceiveId,
user user

View File

@@ -155,7 +155,7 @@ export default class SalesEstimatesController extends BaseController {
check('estimate_date').exists().isISO8601().toDate(), check('estimate_date').exists().isISO8601().toDate(),
check('expiration_date').exists().isISO8601().toDate(), check('expiration_date').exists().isISO8601().toDate(),
check('reference').optional(), check('reference').optional(),
check('estimate_number').optional().trim(), check('estimate_number').optional().trim().escape(),
check('delivered').default(false).isBoolean().toBoolean(), check('delivered').default(false).isBoolean().toBoolean(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(), check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
@@ -170,7 +170,8 @@ export default class SalesEstimatesController extends BaseController {
check('entries.*.rate').exists().isNumeric().toFloat(), check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.description') check('entries.*.description')
.optional({ nullable: true }) .optional({ nullable: true })
.trim(), .trim()
.escape(),
check('entries.*.discount') check('entries.*.discount')
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
@@ -180,9 +181,9 @@ export default class SalesEstimatesController extends BaseController {
.isNumeric() .isNumeric()
.toInt(), .toInt(),
check('note').optional().trim(), check('note').optional().trim().escape(),
check('terms_conditions').optional().trim(), check('terms_conditions').optional().trim().escape(),
check('send_to_email').optional().trim(), check('send_to_email').optional().trim().escape(),
check('attachments').isArray().optional(), check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(), check('attachments.*.key').exists().isString(),

View File

@@ -200,12 +200,12 @@ export default class SaleInvoicesController extends BaseController {
check('customer_id').exists().isNumeric().toInt(), check('customer_id').exists().isNumeric().toInt(),
check('invoice_date').exists().isISO8601().toDate(), check('invoice_date').exists().isISO8601().toDate(),
check('due_date').exists().isISO8601().toDate(), check('due_date').exists().isISO8601().toDate(),
check('invoice_no').optional().trim(), check('invoice_no').optional().trim().escape(),
check('reference_no').optional().trim(), check('reference_no').optional().trim().escape(),
check('delivered').default(false).isBoolean().toBoolean(), check('delivered').default(false).isBoolean().toBoolean(),
check('invoice_message').optional().trim(), check('invoice_message').optional().trim().escape(),
check('terms_conditions').optional().trim(), check('terms_conditions').optional().trim().escape(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(), check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
@@ -226,10 +226,12 @@ export default class SaleInvoicesController extends BaseController {
.toFloat(), .toFloat(),
check('entries.*.description') check('entries.*.description')
.optional({ nullable: true }) .optional({ nullable: true })
.trim(), .trim()
.escape(),
check('entries.*.tax_code') check('entries.*.tax_code')
.optional({ nullable: true }) .optional({ nullable: true })
.trim() .trim()
.escape()
.isString(), .isString(),
check('entries.*.tax_rate_id') check('entries.*.tax_rate_id')
.optional({ nullable: true }) .optional({ nullable: true })

View File

@@ -130,8 +130,8 @@ export default class SalesReceiptsController extends BaseController {
check('deposit_account_id').exists().isNumeric().toInt(), check('deposit_account_id').exists().isNumeric().toInt(),
check('receipt_date').exists().isISO8601(), check('receipt_date').exists().isISO8601(),
check('receipt_number').optional().trim(), check('receipt_number').optional().trim().escape(),
check('reference_no').optional().trim(), check('reference_no').optional().trim().escape(),
check('closed').default(false).isBoolean().toBoolean(), check('closed').default(false).isBoolean().toBoolean(),
check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(), check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(),
@@ -150,13 +150,14 @@ export default class SalesReceiptsController extends BaseController {
.toInt(), .toInt(),
check('entries.*.description') check('entries.*.description')
.optional({ nullable: true }) .optional({ nullable: true })
.trim(), .trim()
.escape(),
check('entries.*.warehouse_id') check('entries.*.warehouse_id')
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
.toInt(), .toInt(),
check('receipt_message').optional().trim(), check('receipt_message').optional().trim().escape(),
check('statement').optional().trim(), check('statement').optional().trim().escape(),
check('attachments').isArray().optional(), check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(), check('attachments.*.key').exists().isString(),
]; ];

View File

@@ -52,7 +52,10 @@ export default class SettingsController extends BaseController {
* Retrieve the application options from the storage. * Retrieve the application options from the storage.
*/ */
private get getSettingsSchema() { private get getSettingsSchema() {
return [query('key').optional().trim(), query('group').optional().trim()]; return [
query('key').optional().trim().escape(),
query('group').optional().trim().escape(),
];
} }
/** /**

View File

@@ -8,7 +8,6 @@ import SubscriptionService from '@/services/Subscription/SubscriptionService';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseController from '../BaseController'; import BaseController from '../BaseController';
import { LemonSqueezyService } from '@/services/Subscription/LemonSqueezyService'; import { LemonSqueezyService } from '@/services/Subscription/LemonSqueezyService';
import { SubscriptionApplication } from '@/services/Subscription/SubscriptionApplication';
@Service() @Service()
export class SubscriptionController extends BaseController { export class SubscriptionController extends BaseController {
@@ -18,9 +17,6 @@ export class SubscriptionController extends BaseController {
@Inject() @Inject()
private lemonSqueezyService: LemonSqueezyService; private lemonSqueezyService: LemonSqueezyService;
@Inject()
private subscriptionApp: SubscriptionApplication;
/** /**
* Router constructor. * Router constructor.
*/ */
@@ -37,14 +33,6 @@ export class SubscriptionController extends BaseController {
this.validationResult, this.validationResult,
this.getCheckoutUrl.bind(this) this.getCheckoutUrl.bind(this)
); );
router.post('/cancel', asyncMiddleware(this.cancelSubscription.bind(this)));
router.post('/resume', asyncMiddleware(this.resumeSubscription.bind(this)));
router.post(
'/change',
[body('variant_id').exists().trim()],
this.validationResult,
asyncMiddleware(this.changeSubscriptionPlan.bind(this))
);
router.get('/', asyncMiddleware(this.getSubscriptions.bind(this))); router.get('/', asyncMiddleware(this.getSubscriptions.bind(this)));
return router; return router;
@@ -97,84 +85,4 @@ export class SubscriptionController extends BaseController {
next(error); next(error);
} }
} }
/**
* Cancels the subscription of the current organization.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response|null>}
*/
private async cancelSubscription(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
try {
await this.subscriptionApp.cancelSubscription(tenantId, '455610');
return res.status(200).send({
status: 200,
message: 'The organization subscription has been canceled.',
});
} catch (error) {
next(error);
}
}
/**
* Resumes the subscription of the current organization.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response | null>}
*/
private async resumeSubscription(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
try {
await this.subscriptionApp.resumeSubscription(tenantId);
return res.status(200).send({
status: 200,
message: 'The organization subscription has been resumed.',
});
} catch (error) {
next(error);
}
}
/**
* Changes the main subscription plan of the current organization.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response | null>}
*/
public async changeSubscriptionPlan(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const body = this.matchedBodyData(req);
try {
await this.subscriptionApp.changeSubscriptionPlan(
tenantId,
body.variantId
);
return res.status(200).send({
message: 'The subscription plan has been changed.',
});
} catch (error) {
next(error);
}
}
} }

View File

@@ -32,7 +32,7 @@ export default class ViewsController extends BaseController {
* Custom views list validation schema. * Custom views list validation schema.
*/ */
get viewsListSchemaValidation() { get viewsListSchemaValidation() {
return [param('resource_model').exists().trim()]; return [param('resource_model').exists().trim().escape()];
} }
/** /**

View File

@@ -236,13 +236,5 @@ module.exports = {
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY, secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
endpoint: process.env.S3_ENDPOINT, endpoint: process.env.S3_ENDPOINT,
bucket: process.env.S3_BUCKET || 'bigcapital-documents', bucket: process.env.S3_BUCKET || 'bigcapital-documents',
forcePathStyle: parseBoolean(
defaultTo(process.env.S3_FORCE_PATH_STYLE, false),
false
),
},
loops: {
apiKey: process.env.LOOPS_API_KEY,
}, },
}; };

View File

@@ -1,16 +1,7 @@
export const CashflowTransactionTypes = {
OtherIncome: 'Other income',
OtherExpense: 'Other expense',
OwnerDrawing: 'Owner drawing',
OwnerContribution: 'Owner contribution',
TransferToAccount: 'Transfer to account',
TransferFromAccount: 'Transfer from account',
};
export const TransactionTypes = { export const TransactionTypes = {
SaleInvoice: 'Sale invoice', SaleInvoice: 'Sale invoice',
SaleReceipt: 'Sale receipt', SaleReceipt: 'Sale receipt',
PaymentReceive: 'Payment received', PaymentReceive: 'Payment receive',
Bill: 'Bill', Bill: 'Bill',
BillPayment: 'Payment made', BillPayment: 'Payment made',
VendorOpeningBalance: 'Vendor opening balance', VendorOpeningBalance: 'Vendor opening balance',
@@ -26,10 +17,12 @@ export const TransactionTypes = {
OtherExpense: 'Other expense', OtherExpense: 'Other expense',
OwnerDrawing: 'Owner drawing', OwnerDrawing: 'Owner drawing',
InvoiceWriteOff: 'Invoice write-off', InvoiceWriteOff: 'Invoice write-off',
CreditNote: 'transaction_type.credit_note', CreditNote: 'transaction_type.credit_note',
VendorCredit: 'transaction_type.vendor_credit', VendorCredit: 'transaction_type.vendor_credit',
RefundCreditNote: 'transaction_type.refund_credit_note', RefundCreditNote: 'transaction_type.refund_credit_note',
RefundVendorCredit: 'transaction_type.refund_vendor_credit', RefundVendorCredit: 'transaction_type.refund_vendor_credit',
LandedCost: 'transaction_type.landed_cost', LandedCost: 'transaction_type.landed_cost',
CashflowTransaction: CashflowTransactionTypes,
}; };

View File

@@ -1,45 +0,0 @@
exports.up = function (knex) {
return knex.schema
.createTable('bank_rules', (table) => {
table.increments('id').primary();
table.string('name');
table.integer('order').unsigned();
table
.integer('apply_if_account_id')
.unsigned()
.references('id')
.inTable('accounts');
table.string('apply_if_transaction_type');
table.string('assign_category');
table
.integer('assign_account_id')
.unsigned()
.references('id')
.inTable('accounts');
table.string('assign_payee');
table.string('assign_memo');
table.string('conditions_type');
table.timestamps();
})
.createTable('bank_rule_conditions', (table) => {
table.increments('id').primary();
table
.integer('rule_id')
.unsigned()
.references('id')
.inTable('bank_rules');
table.string('field');
table.string('comparator');
table.string('value');
});
};
exports.down = function (knex) {
return knex.schema
.dropTableIfExists('bank_rules')
.dropTableIfExists('bank_rule_conditions');
};

View File

@@ -1,31 +0,0 @@
exports.up = function (knex) {
return knex.schema.createTable('recognized_bank_transactions', (table) => {
table.increments('id');
table
.integer('uncategorized_transaction_id')
.unsigned()
.references('id')
.inTable('uncategorized_cashflow_transactions')
.withKeyName('recognizedBankTransactionsUncategorizedTransIdForeign');
table
.integer('bank_rule_id')
.unsigned()
.references('id')
.inTable('bank_rules');
table.string('assigned_category');
table
.integer('assigned_account_id')
.unsigned()
.references('id')
.inTable('accounts');
table.string('assigned_payee');
table.string('assigned_memo');
table.timestamps();
});
};
exports.down = function (knex) {
return knex.schema.dropTableIfExists('recognized_bank_transactions');
};

View File

@@ -1,16 +0,0 @@
exports.up = function (knex) {
return knex.schema.table('uncategorized_cashflow_transactions', (table) => {
table
.integer('recognized_transaction_id')
.unsigned()
.references('id')
.inTable('recognized_bank_transactions')
.withKeyName('uncategorizedCashflowTransRecognizedTranIdForeign');
});
};
exports.down = function (knex) {
return knex.schema.table('uncategorized_cashflow_transactions', (table) => {
table.dropColumn('recognized_transaction_id');
});
};

View File

@@ -1,18 +0,0 @@
exports.up = function (knex) {
return knex.schema.createTable('matched_bank_transactions', (table) => {
table.increments('id');
table
.integer('uncategorized_transaction_id')
.unsigned()
.references('id')
.inTable('uncategorized_cashflow_transactions');
table.string('reference_type');
table.integer('reference_id').unsigned();
table.decimal('amount');
table.timestamps();
});
};
exports.down = function (knex) {
return knex.schema.dropTableIfExists('matched_bank_transactions');
};

View File

@@ -1,11 +0,0 @@
exports.up = function (knex) {
return knex.schema.table('uncategorized_cashflow_transactions', (table) => {
table.datetime('excluded_at');
});
};
exports.down = function (knex) {
return knex.schema.table('uncategorized_cashflow_transactions', (table) => {
table.dropColumn('excluded_at');
});
};

View File

@@ -1,11 +0,0 @@
exports.up = function (knex) {
return knex.schema.table('uncategorized_cashflow_transactions', (table) => {
table.string('batch');
});
};
exports.down = function (knex) {
return knex.schema.table('uncategorized_cashflow_transactions', (table) => {
table.dropColumn('batch');
});
};

View File

@@ -1,11 +0,0 @@
exports.up = function (knex) {
return knex.schema.table('settings', (table) => {
table.text('value').alter();
});
};
exports.down = (knex) => {
return knex.schema.table('settings', (table) => {
table.string('value').alter();
});
};

View File

@@ -1,60 +0,0 @@
exports.up = function (knex) {
return knex('accounts_transactions')
.whereIn('referenceType', [
'OtherIncome',
'OtherExpense',
'OwnerDrawing',
'OwnerContribution',
'TransferToAccount',
'TransferFromAccount',
])
.update({
transactionType: knex.raw(`
CASE
WHEN REFERENCE_TYPE = 'OtherIncome' THEN 'OtherIncome'
WHEN REFERENCE_TYPE = 'OtherExpense' THEN 'OtherExpense'
WHEN REFERENCE_TYPE = 'OwnerDrawing' THEN 'OwnerDrawing'
WHEN REFERENCE_TYPE = 'OwnerContribution' THEN 'OwnerContribution'
WHEN REFERENCE_TYPE = 'TransferToAccount' THEN 'TransferToAccount'
WHEN REFERENCE_TYPE = 'TransferFromAccount' THEN 'TransferFromAccount'
END
`),
referenceType: knex.raw(`
CASE
WHEN REFERENCE_TYPE IN ('OtherIncome', 'OtherExpense', 'OwnerDrawing', 'OwnerContribution', 'TransferToAccount', 'TransferFromAccount') THEN 'CashflowTransaction'
ELSE REFERENCE_TYPE
END
`),
});
};
exports.down = function (knex) {
return knex('accounts_transactions')
.whereIn('transactionType', [
'OtherIncome',
'OtherExpense',
'OwnerDrawing',
'OwnerContribution',
'TransferToAccount',
'TransferFromAccount',
])
.update({
referenceType: knex.raw(`
CASE
WHEN TRANSACTION_TYPE = 'OtherIncome' THEN 'OtherIncome'
WHEN TRANSACTION_TYPE = 'OtherExpense' THEN 'OtherExpense'
WHEN TRANSACTION_TYPE = 'OwnerDrawing' THEN 'OwnerDrawing'
WHEN TRANSACTION_TYPE = 'OwnerContribution' THEN 'OwnerContribution'
WHEN TRANSACTION_TYPE = 'TransferToAccount' THEN 'TransferToAccount'
WHEN TRANSACTION_TYPE = 'TransferFromAccount' THEN 'TransferFromAccount'
ELSE REFERENCE_TYPE
END
`),
transactionType: knex.raw(`
CASE
WHEN TRANSACTION_TYPE IN ('OtherIncome', 'OtherExpense', 'OwnerDrawing', 'OwnerContribution', 'TransferToAccount', 'TransferFromAccount') THEN NULL
ELSE TRANSACTION_TYPE
END
`),
});
};

View File

@@ -1,11 +0,0 @@
exports.up = function (knex) {
return knex.schema.table('accounts', (table) => {
table.string('plaid_item_id').nullable();
});
};
exports.down = function (knex) {
return knex.schema.table('accounts', (table) => {
table.dropColumn('plaid_item_id');
});
};

View File

@@ -1,19 +0,0 @@
exports.up = function (knex) {
return knex.schema
.table('accounts', (table) => {
table
.boolean('is_syncing_owner')
.defaultTo(false)
.after('is_feeds_active');
})
.then(() => {
return knex('accounts')
.whereNotNull('plaid_item_id')
.orWhereNotNull('plaid_account_id')
.update('is_syncing_owner', true);
});
};
exports.down = function (knex) {
table.dropColumn('is_syncing_owner');
};

View File

@@ -1,18 +0,0 @@
// This migration changes the precision of the tax_amount_withheld column in the bills and sales_invoices tables from 8, 2 to 13, 2.
// This migration is necessary to allow tax_amount_withheld filed to store values bigger than 999,999.99.
exports.up = function(knex) {
return knex.schema.alterTable('bills', function (table) {
table.decimal('tax_amount_withheld', 13, 2).alter();
}).alterTable('sales_invoices', function (table) {
table.decimal('tax_amount_withheld', 13, 2).alter();
});
};
exports.down = function(knex) {
return knex.schema.alterTable('bills', function (table) {
table.decimal('tax_amount_withheld', 8, 2).alter();
}).alterTable('sales_invoices', function (table) {
table.decimal('tax_amount_withheld', 8, 2).alter();
});
};

View File

@@ -1,11 +0,0 @@
exports.up = function (knex) {
return knex.schema.table('plaid_items', (table) => {
table.datetime('paused_at');
});
};
exports.down = function (knex) {
return knex.schema.table('plaid_items', (table) => {
table.dropColumn('paused_at');
});
};

View File

@@ -1,13 +0,0 @@
exports.up = function (knex) {
return knex.schema.table('uncategorized_cashflow_transactions', (table) => {
table.boolean('pending').defaultTo(false);
table.string('pending_plaid_transaction_id').nullable();
});
};
exports.down = function (knex) {
return knex.schema.table('uncategorized_cashflow_transactions', (table) => {
table.dropColumn('pending');
table.dropColumn('pending_plaid_transaction_id');
});
};

View File

@@ -12,7 +12,8 @@ export default class SeedAccounts extends TenantSeeder {
description: this.i18n.__(account.description), description: this.i18n.__(account.description),
currencyCode: this.tenant.metadata.baseCurrency, currencyCode: this.tenant.metadata.baseCurrency,
seededAt: new Date(), seededAt: new Date(),
})); })
);
return knex('accounts').then(async () => { return knex('accounts').then(async () => {
// Inserts seed entries. // Inserts seed entries.
return knex('accounts').insert(data); return knex('accounts').insert(data);

View File

@@ -9,28 +9,6 @@ export const TaxPayableAccount = {
predefined: 1, predefined: 1,
}; };
export const UnearnedRevenueAccount = {
name: 'Unearned Revenue',
slug: 'unearned-revenue',
account_type: 'other-current-liability',
parent_account_id: null,
code: '50005',
active: true,
index: 1,
predefined: true,
};
export const PrepardExpenses = {
name: 'Prepaid Expenses',
slug: 'prepaid-expenses',
account_type: 'other-current-asset',
parent_account_id: null,
code: '100010',
active: true,
index: 1,
predefined: true,
};
export default [ export default [
{ {
name: 'Bank Account', name: 'Bank Account',
@@ -345,6 +323,4 @@ export default [
index: 1, index: 1,
predefined: 0, predefined: 0,
}, },
UnearnedRevenueAccount,
PrepardExpenses,
]; ];

View File

@@ -15,7 +15,6 @@ export interface IAccountDTO {
export interface IAccountCreateDTO extends IAccountDTO { export interface IAccountCreateDTO extends IAccountDTO {
currencyCode?: string; currencyCode?: string;
plaidAccountId?: string; plaidAccountId?: string;
plaidItemId?: string;
} }
export interface IAccountEditDTO extends IAccountDTO {} export interface IAccountEditDTO extends IAccountDTO {}
@@ -38,8 +37,6 @@ export interface IAccount {
accountNormal: string; accountNormal: string;
accountParentType: string; accountParentType: string;
bankBalance: string; bankBalance: string;
plaidItemId: number | null
lastFeedsUpdatedAt: Date;
} }
export enum AccountNormal { export enum AccountNormal {
@@ -69,9 +66,7 @@ export interface IAccountTransaction {
referenceId: number; referenceId: number;
referenceNumber?: string; referenceNumber?: string;
transactionNumber?: string; transactionNumber?: string;
transactionType?: string;
note?: string; note?: string;

View File

@@ -1,4 +1,3 @@
import { Knex } from 'knex';
import { import {
IFinancialSheetCommonMeta, IFinancialSheetCommonMeta,
INumberFormatQuery, INumberFormatQuery,
@@ -236,7 +235,6 @@ export interface ICashflowTransactionSchema {
export interface ICashflowTransactionInput extends ICashflowTransactionSchema {} export interface ICashflowTransactionInput extends ICashflowTransactionSchema {}
export interface ICategorizeCashflowTransactioDTO { export interface ICategorizeCashflowTransactioDTO {
date: Date;
creditAccountId: number; creditAccountId: number;
referenceNo: string; referenceNo: string;
transactionNumber: string; transactionNumber: string;
@@ -259,6 +257,7 @@ export interface IUncategorizedCashflowTransaction {
categorized: boolean; categorized: boolean;
} }
export interface CreateUncategorizedTransactionDTO { export interface CreateUncategorizedTransactionDTO {
date: Date | string; date: Date | string;
accountId: number; accountId: number;
@@ -268,34 +267,4 @@ export interface CreateUncategorizedTransactionDTO {
description?: string; description?: string;
referenceNo?: string | null; referenceNo?: string | null;
plaidTransactionId?: string | null; plaidTransactionId?: string | null;
pending?: boolean;
pendingPlaidTransactionId?: string | null;
batch?: string;
}
export interface IUncategorizedTransactionCreatingEventPayload {
tenantId: number;
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO;
trx: Knex.Transaction;
}
export interface IUncategorizedTransactionCreatedEventPayload {
tenantId: number;
uncategorizedTransaction: any;
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO;
trx: Knex.Transaction;
}
export interface IPendingTransactionRemovingEventPayload {
tenantId: number;
uncategorizedTransactionId: number;
pendingTransaction: IUncategorizedCashflowTransaction;
trx?: Knex.Transaction;
}
export interface IPendingTransactionRemovedEventPayload {
tenantId: number;
uncategorizedTransactionId: number;
pendingTransaction: IUncategorizedCashflowTransaction;
trx?: Knex.Transaction;
} }

View File

@@ -130,23 +130,19 @@ export interface ICommandCashflowDeletedPayload {
export interface ICashflowTransactionCategorizedPayload { export interface ICashflowTransactionCategorizedPayload {
tenantId: number; tenantId: number;
uncategorizedTransactions: Array<IUncategorizedCashflowTransaction>; cashflowTransactionId: number;
cashflowTransaction: ICashflowTransaction; cashflowTransaction: ICashflowTransaction;
oldUncategorizedTransactions: Array<IUncategorizedCashflowTransaction>;
categorizeDTO: any;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
export interface ICashflowTransactionUncategorizingPayload { export interface ICashflowTransactionUncategorizingPayload {
tenantId: number; tenantId: number;
uncategorizedTransactionId: number; uncategorizedTransaction: IUncategorizedCashflowTransaction;
oldUncategorizedTransactions: Array<IUncategorizedCashflowTransaction>;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
export interface ICashflowTransactionUncategorizedPayload { export interface ICashflowTransactionUncategorizedPayload {
tenantId: number; tenantId: number;
uncategorizedTransactionId: number; uncategorizedTransaction: IUncategorizedCashflowTransaction;
uncategorizedTransactions: Array<IUncategorizedCashflowTransaction>; oldUncategorizedTransaction: IUncategorizedCashflowTransaction;
oldUncategorizedTransactions: Array<IUncategorizedCashflowTransaction>;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
@@ -168,10 +164,3 @@ export interface IGetUncategorizedTransactionsQuery {
page?: number; page?: number;
pageSize?: number; pageSize?: number;
} }
export interface IGetRecognizedTransactionsQuery {
page?: number;
pageSize?: number;
accountId?: number;
}

View File

@@ -29,9 +29,4 @@ export interface ICashflowAccountTransaction {
date: Date; date: Date;
formattedDate: string; formattedDate: string;
status: string;
formattedStatus: string;
uncategorizedTransactionId: number;
} }

View File

@@ -30,7 +30,7 @@ export interface IGeneralLedgerSheetAccountTransaction {
currencyCode: string; currencyCode: string;
note?: string; note?: string;
transactionTypeFormatted: string; transactionType?: string;
transactionNumber: string; transactionNumber: string;
referenceId?: number; referenceId?: number;

View File

@@ -1,8 +0,0 @@
import { ImportFilePreviewPOJO } from "@/services/Import/interfaces";
export interface IImportFileCommitedEventPayload {
tenantId: number;
importId: number;
meta: ImportFilePreviewPOJO;
}

View File

@@ -40,8 +40,6 @@ export interface ILedgerEntry {
date: Date | string; date: Date | string;
transactionType: string; transactionType: string;
transactionSubType?: string;
transactionId: number; transactionId: number;
transactionNumber?: string; transactionNumber?: string;

View File

@@ -8,7 +8,7 @@ import { ILedgerEntry } from './Ledger';
import { ISaleInvoice } from './SaleInvoice'; import { ISaleInvoice } from './SaleInvoice';
import { AttachmentLinkDTO } from './Attachments'; import { AttachmentLinkDTO } from './Attachments';
export interface IPaymentReceived { export interface IPaymentReceive {
id?: number; id?: number;
customerId: number; customerId: number;
paymentDate: Date; paymentDate: Date;
@@ -19,14 +19,14 @@ export interface IPaymentReceived {
depositAccountId: number; depositAccountId: number;
paymentReceiveNo: string; paymentReceiveNo: string;
statement: string; statement: string;
entries: IPaymentReceivedEntry[]; entries: IPaymentReceiveEntry[];
userId: number; userId: number;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
localAmount?: number; localAmount?: number;
branchId?: number; branchId?: number;
} }
export interface IPaymentReceivedCreateDTO { export interface IPaymentReceiveCreateDTO {
customerId: number; customerId: number;
paymentDate: Date; paymentDate: Date;
amount: number; amount: number;
@@ -35,13 +35,13 @@ export interface IPaymentReceivedCreateDTO {
depositAccountId: number; depositAccountId: number;
paymentReceiveNo?: string; paymentReceiveNo?: string;
statement: string; statement: string;
entries: IPaymentReceivedEntryDTO[]; entries: IPaymentReceiveEntryDTO[];
branchId?: number; branchId?: number;
attachments?: AttachmentLinkDTO[]; attachments?: AttachmentLinkDTO[];
} }
export interface IPaymentReceivedEditDTO { export interface IPaymentReceiveEditDTO {
customerId: number; customerId: number;
paymentDate: Date; paymentDate: Date;
amount: number; amount: number;
@@ -50,12 +50,12 @@ export interface IPaymentReceivedEditDTO {
depositAccountId: number; depositAccountId: number;
paymentReceiveNo?: string; paymentReceiveNo?: string;
statement: string; statement: string;
entries: IPaymentReceivedEntryDTO[]; entries: IPaymentReceiveEntryDTO[];
branchId?: number; branchId?: number;
attachments?: AttachmentLinkDTO[]; attachments?: AttachmentLinkDTO[];
} }
export interface IPaymentReceivedEntry { export interface IPaymentReceiveEntry {
id?: number; id?: number;
paymentReceiveId: number; paymentReceiveId: number;
invoiceId: number; invoiceId: number;
@@ -64,7 +64,7 @@ export interface IPaymentReceivedEntry {
invoice?: ISaleInvoice; invoice?: ISaleInvoice;
} }
export interface IPaymentReceivedEntryDTO { export interface IPaymentReceiveEntryDTO {
id?: number; id?: number;
index: number; index: number;
paymentReceiveId: number; paymentReceiveId: number;
@@ -72,7 +72,7 @@ export interface IPaymentReceivedEntryDTO {
paymentAmount: number; paymentAmount: number;
} }
export interface IPaymentsReceivedFilter extends IDynamicListFilterDTO { export interface IPaymentReceivesFilter extends IDynamicListFilterDTO {
stringifiedFilterRoles?: string; stringifiedFilterRoles?: string;
} }
@@ -88,65 +88,65 @@ export interface IPaymentReceivePageEntry {
date: Date | string; date: Date | string;
} }
export interface IPaymentReceivedEditPage { export interface IPaymentReceiveEditPage {
paymentReceive: IPaymentReceived; paymentReceive: IPaymentReceive;
entries: IPaymentReceivePageEntry[]; entries: IPaymentReceivePageEntry[];
} }
export interface IPaymentsReceivedService { export interface IPaymentsReceiveService {
validateCustomerHasNoPayments( validateCustomerHasNoPayments(
tenantId: number, tenantId: number,
customerId: number customerId: number
): Promise<void>; ): Promise<void>;
} }
export interface IPaymentReceivedSmsDetails { export interface IPaymentReceiveSmsDetails {
customerName: string; customerName: string;
customerPhoneNumber: string; customerPhoneNumber: string;
smsMessage: string; smsMessage: string;
} }
export interface IPaymentReceivedCreatingPayload { export interface IPaymentReceiveCreatingPayload {
tenantId: number; tenantId: number;
paymentReceiveDTO: IPaymentReceivedCreateDTO; paymentReceiveDTO: IPaymentReceiveCreateDTO;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
export interface IPaymentReceivedCreatedPayload { export interface IPaymentReceiveCreatedPayload {
tenantId: number; tenantId: number;
paymentReceive: IPaymentReceived; paymentReceive: IPaymentReceive;
paymentReceiveId: number; paymentReceiveId: number;
authorizedUser: ISystemUser; authorizedUser: ISystemUser;
paymentReceiveDTO: IPaymentReceivedCreateDTO; paymentReceiveDTO: IPaymentReceiveCreateDTO;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
export interface IPaymentReceivedEditedPayload { export interface IPaymentReceiveEditedPayload {
tenantId: number; tenantId: number;
paymentReceiveId: number; paymentReceiveId: number;
paymentReceive: IPaymentReceived; paymentReceive: IPaymentReceive;
oldPaymentReceive: IPaymentReceived; oldPaymentReceive: IPaymentReceive;
paymentReceiveDTO: IPaymentReceivedEditDTO; paymentReceiveDTO: IPaymentReceiveEditDTO;
authorizedUser: ISystemUser; authorizedUser: ISystemUser;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
export interface IPaymentReceivedEditingPayload { export interface IPaymentReceiveEditingPayload {
tenantId: number; tenantId: number;
oldPaymentReceive: IPaymentReceived; oldPaymentReceive: IPaymentReceive;
paymentReceiveDTO: IPaymentReceivedEditDTO; paymentReceiveDTO: IPaymentReceiveEditDTO;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
export interface IPaymentReceivedDeletingPayload { export interface IPaymentReceiveDeletingPayload {
tenantId: number; tenantId: number;
oldPaymentReceive: IPaymentReceived; oldPaymentReceive: IPaymentReceive;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
export interface IPaymentReceivedDeletedPayload { export interface IPaymentReceiveDeletedPayload {
tenantId: number; tenantId: number;
paymentReceiveId: number; paymentReceiveId: number;
oldPaymentReceive: IPaymentReceived; oldPaymentReceive: IPaymentReceive;
authorizedUser: ISystemUser; authorizedUser: ISystemUser;
trx: Knex.Transaction; trx: Knex.Transaction;
} }

View File

@@ -1,5 +1,3 @@
import { Knex } from "knex";
export interface IPlaidItemCreatedEventPayload { export interface IPlaidItemCreatedEventPayload {
tenantId: number; tenantId: number;
plaidAccessToken: string; plaidAccessToken: string;
@@ -56,10 +54,3 @@ export interface SyncAccountsTransactionsTask {
plaidAccountId: number; plaidAccountId: number;
plaidTransactions: PlaidTransaction[]; plaidTransactions: PlaidTransaction[];
} }
export interface IPlaidTransactionsSyncedEventPayload {
tenantId: number;
plaidAccountId: number;
batch: string;
trx?: Knex.Transaction
}

View File

@@ -1,12 +1,69 @@
import { forEach } from 'lodash';
import { Configuration, PlaidApi, PlaidEnvironments } from 'plaid'; import { Configuration, PlaidApi, PlaidEnvironments } from 'plaid';
import { createPlaidApiEvent } from './PlaidApiEventsDBSync';
import config from '@/config'; import config from '@/config';
const OPTIONS = { clientApp: 'Plaid-Pattern' };
// We want to log requests to / responses from the Plaid API (via the Plaid client), as this data
// can be useful for troubleshooting.
/**
* Logging function for Plaid client methods that use an access_token as an argument. Associates
* the Plaid API event log entry with the item and user the request is for.
*
* @param {string} clientMethod the name of the Plaid client method called.
* @param {Array} clientMethodArgs the arguments passed to the Plaid client method.
* @param {Object} response the response from the Plaid client.
*/
const defaultLogger = async (clientMethod, clientMethodArgs, response) => {
const accessToken = clientMethodArgs[0].access_token;
// const { id: itemId, user_id: userId } = await retrieveItemByPlaidAccessToken(
// accessToken
// );
// await createPlaidApiEvent(1, 1, clientMethod, clientMethodArgs, response);
// console.log(response);
};
/**
* Logging function for Plaid client methods that do not use access_token as an argument. These
* Plaid API event log entries will not be associated with an item or user.
*
* @param {string} clientMethod the name of the Plaid client method called.
* @param {Array} clientMethodArgs the arguments passed to the Plaid client method.
* @param {Object} response the response from the Plaid client.
*/
const noAccessTokenLogger = async (
clientMethod,
clientMethodArgs,
response
) => {
// console.log(response);
// await createPlaidApiEvent(
// undefined,
// undefined,
// clientMethod,
// clientMethodArgs,
// response
// );
};
// Plaid client methods used in this app, mapped to their appropriate logging functions.
const clientMethodLoggingFns = {
accountsGet: defaultLogger,
institutionsGet: noAccessTokenLogger,
institutionsGetById: noAccessTokenLogger,
itemPublicTokenExchange: noAccessTokenLogger,
itemRemove: defaultLogger,
linkTokenCreate: noAccessTokenLogger,
transactionsSync: defaultLogger,
sandboxItemResetLogin: defaultLogger,
};
// Wrapper for the Plaid client. This allows us to easily log data for all Plaid client requests. // Wrapper for the Plaid client. This allows us to easily log data for all Plaid client requests.
export class PlaidClientWrapper { export class PlaidClientWrapper {
private static instance: PlaidClientWrapper; constructor() {
private client: PlaidApi;
private constructor() {
// Initialize the Plaid client. // Initialize the Plaid client.
const configuration = new Configuration({ const configuration = new Configuration({
basePath: PlaidEnvironments[config.plaid.env], basePath: PlaidEnvironments[config.plaid.env],
@@ -18,13 +75,26 @@ export class PlaidClientWrapper {
}, },
}, },
}); });
this.client = new PlaidApi(configuration); this.client = new PlaidApi(configuration);
// Wrap the Plaid client methods to add a logging function.
forEach(clientMethodLoggingFns, (logFn, method) => {
this[method] = this.createWrappedClientMethod(method, logFn);
});
} }
public static getClient(): PlaidApi { // Allows us to log API request data for troubleshooting purposes.
if (!PlaidClientWrapper.instance) { createWrappedClientMethod(clientMethod, log) {
PlaidClientWrapper.instance = new PlaidClientWrapper(); return async (...args) => {
} try {
return PlaidClientWrapper.instance.client; const res = await this.client[clientMethod](...args);
await log(clientMethod, args, res);
return res;
} catch (err) {
await log(clientMethod, args, err?.response?.data);
throw err;
}
};
} }
} }

View File

@@ -8,5 +8,4 @@ export const s3 = new S3Client({
secretAccessKey: config.s3.secretAccessKey, secretAccessKey: config.s3.secretAccessKey,
}, },
endpoint: config.s3.endpoint, endpoint: config.s3.endpoint,
forcePathStyle: config.s3.forcePathStyle,
}); });

View File

@@ -169,8 +169,8 @@ export class Transformer {
* @param number * @param number
* @returns * @returns
*/ */
protected formatNumber(number, props?) { protected formatNumber(number) {
return formatNumber(number, { money: false, ...props }); return formatNumber(number, { money: false });
} }
/** /**

View File

@@ -102,19 +102,6 @@ import { AttachmentsOnVendorCredits } from '@/services/Attachments/events/Attach
import { AttachmentsOnCreditNote } from '@/services/Attachments/events/AttachmentsOnCreditNote'; import { AttachmentsOnCreditNote } from '@/services/Attachments/events/AttachmentsOnCreditNote';
import { AttachmentsOnBillPayments } from '@/services/Attachments/events/AttachmentsOnPaymentsMade'; import { AttachmentsOnBillPayments } from '@/services/Attachments/events/AttachmentsOnPaymentsMade';
import { AttachmentsOnSaleEstimates } from '@/services/Attachments/events/AttachmentsOnSaleEstimates'; import { AttachmentsOnSaleEstimates } from '@/services/Attachments/events/AttachmentsOnSaleEstimates';
import { TriggerRecognizedTransactions } from '@/services/Banking/RegonizeTranasctions/events/TriggerRecognizedTransactions';
import { ValidateMatchingOnExpenseDelete } from '@/services/Banking/Matching/events/ValidateMatchingOnExpenseDelete';
import { ValidateMatchingOnManualJournalDelete } from '@/services/Banking/Matching/events/ValidateMatchingOnManualJournalDelete';
import { ValidateMatchingOnPaymentReceivedDelete } from '@/services/Banking/Matching/events/ValidateMatchingOnPaymentReceivedDelete';
import { ValidateMatchingOnPaymentMadeDelete } from '@/services/Banking/Matching/events/ValidateMatchingOnPaymentMadeDelete';
import { ValidateMatchingOnCashflowDelete } from '@/services/Banking/Matching/events/ValidateMatchingOnCashflowDelete';
import { RecognizeSyncedBankTranasctions } from '@/services/Banking/Plaid/subscribers/RecognizeSyncedBankTransactions';
import { UnlinkBankRuleOnDeleteBankRule } from '@/services/Banking/Rules/events/UnlinkBankRuleOnDeleteBankRule';
import { DecrementUncategorizedTransactionOnMatching } from '@/services/Banking/Matching/events/DecrementUncategorizedTransactionsOnMatch';
import { DecrementUncategorizedTransactionOnExclude } from '@/services/Banking/Exclude/events/DecrementUncategorizedTransactionOnExclude';
import { DecrementUncategorizedTransactionOnCategorize } from '@/services/Cashflow/subscribers/DecrementUncategorizedTransactionOnCategorize';
import { DisconnectPlaidItemOnAccountDeleted } from '@/services/Banking/BankAccounts/events/DisconnectPlaidItemOnAccountDeleted';
import { LoopsEventsSubscriber } from '@/services/Loops/LoopsEventsSubscriber';
export default () => { export default () => {
return new EventPublisher(); return new EventPublisher();
@@ -259,26 +246,5 @@ export const susbcribers = () => {
AttachmentsOnBillPayments, AttachmentsOnBillPayments,
AttachmentsOnManualJournals, AttachmentsOnManualJournals,
AttachmentsOnExpenses, AttachmentsOnExpenses,
// Bank Rules
TriggerRecognizedTransactions,
UnlinkBankRuleOnDeleteBankRule,
DecrementUncategorizedTransactionOnMatching,
DecrementUncategorizedTransactionOnExclude,
DecrementUncategorizedTransactionOnCategorize,
// Validate matching
ValidateMatchingOnCashflowDelete,
ValidateMatchingOnExpenseDelete,
ValidateMatchingOnManualJournalDelete,
ValidateMatchingOnPaymentReceivedDelete,
ValidateMatchingOnPaymentMadeDelete,
// Plaid
RecognizeSyncedBankTranasctions,
DisconnectPlaidItemOnAccountDeleted,
// Loops
LoopsEventsSubscriber
]; ];
}; };

View File

@@ -34,4 +34,4 @@
// import 'services/Sales/SaleInvoiceWriteoffSubscriber'; // import 'services/Sales/SaleInvoiceWriteoffSubscriber';
// import 'subscribers/SaleInvoices/SendSmsNotificationToCustomer'; // import 'subscribers/SaleInvoices/SendSmsNotificationToCustomer';
// import 'subscribers/SaleReceipt/SendNotificationToCustomer'; // import 'subscribers/SaleReceipt/SendNotificationToCustomer';
// import 'services/Sales/PaymentReceived/PaymentReceiveSmsSubscriber'; // import 'services/Sales/PaymentReceives/PaymentReceiveSmsSubscriber';

View File

@@ -9,13 +9,10 @@ import { SendSaleInvoiceMailJob } from '@/services/Sales/Invoices/SendSaleInvoic
import { SendSaleInvoiceReminderMailJob } from '@/services/Sales/Invoices/SendSaleInvoiceMailReminderJob'; import { SendSaleInvoiceReminderMailJob } from '@/services/Sales/Invoices/SendSaleInvoiceMailReminderJob';
import { SendSaleEstimateMailJob } from '@/services/Sales/Estimates/SendSaleEstimateMailJob'; import { SendSaleEstimateMailJob } from '@/services/Sales/Estimates/SendSaleEstimateMailJob';
import { SaleReceiptMailNotificationJob } from '@/services/Sales/Receipts/SaleReceiptMailNotificationJob'; import { SaleReceiptMailNotificationJob } from '@/services/Sales/Receipts/SaleReceiptMailNotificationJob';
import { PaymentReceivedMailNotificationJob } from '@/services/Sales/PaymentReceived/PaymentReceivedMailNotificationJob'; import { PaymentReceiveMailNotificationJob } from '@/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob';
import { PlaidFetchTransactionsJob } from '@/services/Banking/Plaid/PlaidFetchTransactionsJob'; import { PlaidFetchTransactionsJob } from '@/services/Banking/Plaid/PlaidFetchTransactionsJob';
import { ImportDeleteExpiredFilesJobs } from '@/services/Import/jobs/ImportDeleteExpiredFilesJob'; import { ImportDeleteExpiredFilesJobs } from '@/services/Import/jobs/ImportDeleteExpiredFilesJob';
import { SendVerifyMailJob } from '@/services/Authentication/jobs/SendVerifyMailJob'; import { SendVerifyMailJob } from '@/services/Authentication/jobs/SendVerifyMailJob';
import { ReregonizeTransactionsJob } from '@/services/Banking/RegonizeTranasctions/jobs/RerecognizeTransactionsJob';
import { RegonizeTransactionsJob } from '@/services/Banking/RegonizeTranasctions/jobs/RecognizeTransactionsJob';
import { RevertRegonizeTransactionsJob } from '@/services/Banking/RegonizeTranasctions/jobs/RevertRecognizedTransactionsJob';
export default ({ agenda }: { agenda: Agenda }) => { export default ({ agenda }: { agenda: Agenda }) => {
new ResetPasswordMailJob(agenda); new ResetPasswordMailJob(agenda);
@@ -28,13 +25,10 @@ export default ({ agenda }: { agenda: Agenda }) => {
new SendSaleInvoiceReminderMailJob(agenda); new SendSaleInvoiceReminderMailJob(agenda);
new SendSaleEstimateMailJob(agenda); new SendSaleEstimateMailJob(agenda);
new SaleReceiptMailNotificationJob(agenda); new SaleReceiptMailNotificationJob(agenda);
new PaymentReceivedMailNotificationJob(agenda); new PaymentReceiveMailNotificationJob(agenda);
new PlaidFetchTransactionsJob(agenda); new PlaidFetchTransactionsJob(agenda);
new ImportDeleteExpiredFilesJobs(agenda); new ImportDeleteExpiredFilesJobs(agenda);
new SendVerifyMailJob(agenda); new SendVerifyMailJob(agenda);
new RegonizeTransactionsJob(agenda);
new ReregonizeTransactionsJob(agenda);
new RevertRegonizeTransactionsJob(agenda);
agenda.start().then(() => { agenda.start().then(() => {
agenda.every('1 hours', 'delete-expired-imported-files', {}); agenda.every('1 hours', 'delete-expired-imported-files', {});

View File

@@ -64,10 +64,6 @@ import PlaidItem from 'models/PlaidItem';
import UncategorizedCashflowTransaction from 'models/UncategorizedCashflowTransaction'; import UncategorizedCashflowTransaction from 'models/UncategorizedCashflowTransaction';
import Document from '@/models/Document'; import Document from '@/models/Document';
import DocumentLink from '@/models/DocumentLink'; import DocumentLink from '@/models/DocumentLink';
import { BankRule } from '@/models/BankRule';
import { BankRuleCondition } from '@/models/BankRuleCondition';
import { RecognizedBankTransaction } from '@/models/RecognizedBankTransaction';
import { MatchedBankTransaction } from '@/models/MatchedBankTransaction';
export default (knex) => { export default (knex) => {
const models = { const models = {
@@ -135,10 +131,6 @@ export default (knex) => {
DocumentLink, DocumentLink,
PlaidItem, PlaidItem,
UncategorizedCashflowTransaction, UncategorizedCashflowTransaction,
BankRule,
BankRuleCondition,
RecognizedBankTransaction,
MatchedBankTransaction,
}; };
return mapValues(models, (model) => model.bindKnex(knex)); return mapValues(models, (model) => model.bindKnex(knex));
}; };

View File

@@ -197,7 +197,6 @@ export default class Account extends mixin(TenantModel, [
const ExpenseEntry = require('models/ExpenseCategory'); const ExpenseEntry = require('models/ExpenseCategory');
const ItemEntry = require('models/ItemEntry'); const ItemEntry = require('models/ItemEntry');
const UncategorizedTransaction = require('models/UncategorizedCashflowTransaction'); const UncategorizedTransaction = require('models/UncategorizedCashflowTransaction');
const PlaidItem = require('models/PlaidItem');
return { return {
/** /**
@@ -322,18 +321,6 @@ export default class Account extends mixin(TenantModel, [
query.where('categorized', false); query.where('categorized', false);
}, },
}, },
/**
* Account model may belongs to a Plaid item.
*/
plaidItem: {
relation: Model.BelongsToOneRelation,
modelClass: PlaidItem.default,
join: {
from: 'accounts.plaidItemId',
to: 'plaid_items.plaidItemId',
},
},
}; };
} }

View File

@@ -10,7 +10,6 @@ export default class AccountTransaction extends TenantModel {
debit: number; debit: number;
exchangeRate: number; exchangeRate: number;
taxRate: number; taxRate: number;
transactionType: string;
/** /**
* Table name * Table name
@@ -54,7 +53,7 @@ export default class AccountTransaction extends TenantModel {
* @return {string} * @return {string}
*/ */
get referenceTypeFormatted() { get referenceTypeFormatted() {
return getTransactionTypeLabel(this.referenceType, this.transactionType); return getTransactionTypeLabel(this.referenceType);
} }
/** /**

View File

@@ -1,70 +0,0 @@
import TenantModel from 'models/TenantModel';
import { Model } from 'objection';
export class BankRule extends TenantModel {
id!: number;
name!: string;
order!: number;
applyIfAccountId!: number;
applyIfTransactionType!: string;
assignCategory!: string;
assignAccountId!: number;
assignPayee!: string;
assignMemo!: string;
conditionsType!: string;
/**
* Table name
*/
static get tableName() {
return 'bank_rules';
}
/**
* Timestamps columns.
*/
get timestamps() {
return ['created_at', 'updated_at'];
}
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return [];
}
/**
* Relationship mapping.
*/
static get relationMappings() {
const { BankRuleCondition } = require('models/BankRuleCondition');
const Account = require('models/Account');
return {
/**
* Sale invoice associated entries.
*/
conditions: {
relation: Model.HasManyRelation,
modelClass: BankRuleCondition,
join: {
from: 'bank_rules.id',
to: 'bank_rule_conditions.ruleId',
},
},
/**
* Bank rule may associated to the assign account.
*/
assignAccount: {
relation: Model.BelongsToOneRelation,
modelClass: Account.default,
join: {
from: 'bank_rules.assignAccountId',
to: 'accounts.id',
},
},
};
}
}

View File

@@ -1,24 +0,0 @@
import TenantModel from 'models/TenantModel';
export class BankRuleCondition extends TenantModel {
/**
* Table name.
*/
static get tableName() {
return 'bank_rule_conditions';
}
/**
* Timestamps columns.
*/
get timestamps() {
return [];
}
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return [];
}
}

View File

@@ -404,7 +404,6 @@ export default class Bill extends mixin(TenantModel, [
const Branch = require('models/Branch'); const Branch = require('models/Branch');
const TaxRateTransaction = require('models/TaxRateTransaction'); const TaxRateTransaction = require('models/TaxRateTransaction');
const Document = require('models/Document'); const Document = require('models/Document');
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
return { return {
vendor: { vendor: {
@@ -486,21 +485,6 @@ export default class Bill extends mixin(TenantModel, [
query.where('model_ref', 'Bill'); query.where('model_ref', 'Bill');
}, },
}, },
/**
* Bill may belongs to matched bank transaction.
*/
matchedBankTransaction: {
relation: Model.HasManyRelation,
modelClass: MatchedBankTransaction,
join: {
from: 'bills.id',
to: 'matched_bank_transactions.referenceId',
},
filter(query) {
query.where('reference_type', 'Bill');
},
},
}; };
} }
@@ -525,9 +509,9 @@ export default class Bill extends mixin(TenantModel, [
return notFoundBillsIds; return notFoundBillsIds;
} }
static changePaymentAmount(billId, amount, trx) { static changePaymentAmount(billId, amount) {
const changeMethod = amount > 0 ? 'increment' : 'decrement'; const changeMethod = amount > 0 ? 'increment' : 'decrement';
return this.query(trx) return this.query()
.where('id', billId) .where('id', billId)
[changeMethod]('payment_amount', Math.abs(amount)); [changeMethod]('payment_amount', Math.abs(amount));
} }

View File

@@ -3,7 +3,7 @@ import TenantModel from 'models/TenantModel';
import ModelSetting from './ModelSetting'; import ModelSetting from './ModelSetting';
import BillPaymentSettings from './BillPayment.Settings'; import BillPaymentSettings from './BillPayment.Settings';
import CustomViewBaseModel from './CustomViewBaseModel'; import CustomViewBaseModel from './CustomViewBaseModel';
import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceived/constants'; import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceives/constants';
import ModelSearchable from './ModelSearchable'; import ModelSearchable from './ModelSearchable';
export default class BillPayment extends mixin(TenantModel, [ export default class BillPayment extends mixin(TenantModel, [

View File

@@ -5,9 +5,9 @@ import {
getCashflowAccountTransactionsTypes, getCashflowAccountTransactionsTypes,
getCashflowTransactionType, getCashflowTransactionType,
} from '@/services/Cashflow/utils'; } from '@/services/Cashflow/utils';
import AccountTransaction from './AccountTransaction';
import { CASHFLOW_DIRECTION } from '@/services/Cashflow/constants'; import { CASHFLOW_DIRECTION } from '@/services/Cashflow/constants';
import { getCashflowTransactionFormattedType } from '@/utils/transactions-types'; import { getTransactionTypeLabel } from '@/utils/transactions-types';
export default class CashflowTransaction extends TenantModel { export default class CashflowTransaction extends TenantModel {
transactionType: string; transactionType: string;
amount: number; amount: number;
@@ -64,7 +64,7 @@ export default class CashflowTransaction extends TenantModel {
* @returns {string} * @returns {string}
*/ */
get transactionTypeFormatted() { get transactionTypeFormatted() {
return getCashflowTransactionFormattedType(this.transactionType); return getTransactionTypeLabel(this.transactionType);
} }
get typeMeta() { get typeMeta() {
@@ -95,34 +95,6 @@ export default class CashflowTransaction extends TenantModel {
return !!this.uncategorizedTransaction; return !!this.uncategorizedTransaction;
} }
/**
* Model modifiers.
*/
static get modifiers() {
return {
/**
* Filter the published transactions.
*/
published(query) {
query.whereNot('published_at', null);
},
/**
* Filter the not categorized transactions.
*/
notCategorized(query) {
query.whereNull('cashflowTransactions.uncategorizedTransactionId');
},
/**
* Filter the categorized transactions.
*/
categorized(query) {
query.whereNotNull('cashflowTransactions.uncategorizedTransactionId');
},
};
}
/** /**
* Relationship mapping. * Relationship mapping.
*/ */
@@ -130,7 +102,6 @@ export default class CashflowTransaction extends TenantModel {
const CashflowTransactionLine = require('models/CashflowTransactionLine'); const CashflowTransactionLine = require('models/CashflowTransactionLine');
const AccountTransaction = require('models/AccountTransaction'); const AccountTransaction = require('models/AccountTransaction');
const Account = require('models/Account'); const Account = require('models/Account');
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
return { return {
/** /**
@@ -159,7 +130,8 @@ export default class CashflowTransaction extends TenantModel {
to: 'accounts_transactions.referenceId', to: 'accounts_transactions.referenceId',
}, },
filter(builder) { filter(builder) {
builder.where('reference_type', 'CashflowTransaction'); const referenceTypes = getCashflowAccountTransactionsTypes();
builder.whereIn('reference_type', referenceTypes);
}, },
}, },
@@ -186,22 +158,6 @@ export default class CashflowTransaction extends TenantModel {
to: 'accounts.id', to: 'accounts.id',
}, },
}, },
/**
* Cashflow transaction may belongs to matched bank transaction.
*/
matchedBankTransaction: {
relation: Model.HasManyRelation,
modelClass: MatchedBankTransaction,
join: {
from: 'cashflow_transactions.id',
to: 'matched_bank_transactions.referenceId',
},
filter: (query) => {
const referenceTypes = getCashflowAccountTransactionsTypes();
query.whereIn('reference_type', referenceTypes);
},
},
}; };
} }
} }

View File

@@ -182,7 +182,6 @@ export default class Expense extends mixin(TenantModel, [
const ExpenseCategory = require('models/ExpenseCategory'); const ExpenseCategory = require('models/ExpenseCategory');
const Document = require('models/Document'); const Document = require('models/Document');
const Branch = require('models/Branch'); const Branch = require('models/Branch');
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
return { return {
paymentAccount: { paymentAccount: {
@@ -235,21 +234,6 @@ export default class Expense extends mixin(TenantModel, [
query.where('model_ref', 'Expense'); query.where('model_ref', 'Expense');
}, },
}, },
/**
* Expense may belongs to matched bank transaction.
*/
matchedBankTransaction: {
relation: Model.HasManyRelation,
modelClass: MatchedBankTransaction,
join: {
from: 'expenses_transactions.id',
to: 'matched_bank_transactions.referenceId',
},
filter(query) {
query.where('reference_type', 'Expense');
},
},
}; };
} }

View File

@@ -97,7 +97,6 @@ export default class ManualJournal extends mixin(TenantModel, [
const AccountTransaction = require('models/AccountTransaction'); const AccountTransaction = require('models/AccountTransaction');
const ManualJournalEntry = require('models/ManualJournalEntry'); const ManualJournalEntry = require('models/ManualJournalEntry');
const Document = require('models/Document'); const Document = require('models/Document');
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
return { return {
entries: { entries: {
@@ -141,21 +140,6 @@ export default class ManualJournal extends mixin(TenantModel, [
query.where('model_ref', 'ManualJournal'); query.where('model_ref', 'ManualJournal');
}, },
}, },
/**
* Manual journal may belongs to matched bank transaction.
*/
matchedBankTransaction: {
relation: Model.BelongsToOneRelation,
modelClass: MatchedBankTransaction,
join: {
from: 'manual_journals.id',
to: 'matched_bank_transactions.referenceId',
},
filter(query) {
query.where('reference_type', 'ManualJournal');
},
},
}; };
} }

View File

@@ -1,32 +0,0 @@
import TenantModel from 'models/TenantModel';
import { Model } from 'objection';
export class MatchedBankTransaction extends TenantModel {
/**
* Table name.
*/
static get tableName() {
return 'matched_bank_transactions';
}
/**
* Timestamps columns.
*/
get timestamps() {
return ['createdAt', 'updatedAt'];
}
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return [];
}
/**
* Relationship mapping.
*/
static get relationMappings() {
return {};
}
}

View File

@@ -3,7 +3,7 @@ import TenantModel from 'models/TenantModel';
import ModelSetting from './ModelSetting'; import ModelSetting from './ModelSetting';
import PaymentReceiveSettings from './PaymentReceive.Settings'; import PaymentReceiveSettings from './PaymentReceive.Settings';
import CustomViewBaseModel from './CustomViewBaseModel'; import CustomViewBaseModel from './CustomViewBaseModel';
import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceived/constants'; import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceives/constants';
import ModelSearchable from './ModelSearchable'; import ModelSearchable from './ModelSearchable';
export default class PaymentReceive extends mixin(TenantModel, [ export default class PaymentReceive extends mixin(TenantModel, [

View File

@@ -1,8 +1,6 @@
import TenantModel from 'models/TenantModel'; import TenantModel from 'models/TenantModel';
export default class PlaidItem extends TenantModel { export default class PlaidItem extends TenantModel {
pausedAt: Date;
/** /**
* Table name. * Table name.
*/ */
@@ -23,19 +21,4 @@ export default class PlaidItem extends TenantModel {
static get relationMappings() { static get relationMappings() {
return {}; return {};
} }
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return ['isPaused'];
}
/**
* Detarmines whether the Plaid item feeds syncing is paused.
* @return {boolean}
*/
get isPaused() {
return !!this.pausedAt;
}
} }

View File

@@ -1,72 +0,0 @@
import TenantModel from 'models/TenantModel';
import { Model } from 'objection';
export class RecognizedBankTransaction extends TenantModel {
/**
* Table name.
*/
static get tableName() {
return 'recognized_bank_transactions';
}
/**
* Timestamps columns.
*/
get timestamps() {
return [];
}
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return [];
}
/**
* Relationship mapping.
*/
static get relationMappings() {
const UncategorizedCashflowTransaction = require('./UncategorizedCashflowTransaction');
const Account = require('./Account');
const { BankRule } = require('./BankRule');
return {
/**
* Recognized bank transaction may belongs to uncategorized transactions.
*/
uncategorizedTransactions: {
relation: Model.HasManyRelation,
modelClass: UncategorizedCashflowTransaction.default,
join: {
from: 'recognized_bank_transactions.uncategorizedTransactionId',
to: 'uncategorized_cashflow_transactions.id',
},
},
/**
* Recognized bank transaction may belongs to assign account.
*/
assignAccount: {
relation: Model.BelongsToOneRelation,
modelClass: Account.default,
join: {
from: 'recognized_bank_transactions.assignedAccountId',
to: 'accounts.id',
},
},
/**
* Recognized bank transaction may belongs to bank rule.
*/
bankRule: {
relation: Model.BelongsToOneRelation,
modelClass: BankRule,
join: {
from: 'recognized_bank_transactions.bankRuleId',
to: 'bank_rules.id',
},
},
};
}
}

View File

@@ -411,7 +411,6 @@ export default class SaleInvoice extends mixin(TenantModel, [
const Account = require('models/Account'); const Account = require('models/Account');
const TaxRateTransaction = require('models/TaxRateTransaction'); const TaxRateTransaction = require('models/TaxRateTransaction');
const Document = require('models/Document'); const Document = require('models/Document');
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
return { return {
/** /**
@@ -544,21 +543,6 @@ export default class SaleInvoice extends mixin(TenantModel, [
query.where('model_ref', 'SaleInvoice'); query.where('model_ref', 'SaleInvoice');
}, },
}, },
/**
* Sale invocie may belongs to matched bank transaction.
*/
matchedBankTransaction: {
relation: Model.HasManyRelation,
modelClass: MatchedBankTransaction,
join: {
from: 'sales_invoices.id',
to: "matched_bank_transactions.referenceId",
},
filter(query) {
query.where('reference_type', 'SaleInvoice');
},
},
}; };
} }

View File

@@ -1,69 +0,0 @@
export default {
defaultSort: {
sortOrder: 'DESC',
sortField: 'created_at',
},
exportable: true,
importable: true,
print: {
pageTitle: 'Tax Rates',
},
columns: {
name: {
name: 'Tax Rate Name',
type: 'text',
accessor: 'name',
},
code: {
name: 'Code',
type: 'text',
accessor: 'code',
},
rate: {
name: 'Rate',
type: 'text',
},
description: {
name: 'Description',
type: 'text',
},
isNonRecoverable: {
name: 'Is Non Recoverable',
type: 'boolean',
},
active: {
name: 'Active',
type: 'boolean',
},
},
field: {},
fields2: {
name: {
name: 'Tax name',
fieldType: 'name',
required: true,
},
code: {
name: 'Code',
fieldType: 'code',
required: true,
},
rate: {
name: 'Rate',
fieldType: 'number',
required: true,
},
description: {
name: 'Description',
fieldType: 'text',
},
isNonRecoverable: {
name: 'Is Non Recoverable',
fieldType: 'boolean',
},
active: {
name: 'Active',
fieldType: 'boolean',
},
},
};

View File

@@ -2,13 +2,8 @@ import { mixin, Model, raw } from 'objection';
import TenantModel from 'models/TenantModel'; import TenantModel from 'models/TenantModel';
import ModelSearchable from './ModelSearchable'; import ModelSearchable from './ModelSearchable';
import SoftDeleteQueryBuilder from '@/collection/SoftDeleteQueryBuilder'; import SoftDeleteQueryBuilder from '@/collection/SoftDeleteQueryBuilder';
import TaxRateMeta from './TaxRate.settings';
import ModelSetting from './ModelSetting';
export default class TaxRate extends mixin(TenantModel, [ export default class TaxRate extends mixin(TenantModel, [ModelSearchable]) {
ModelSetting,
ModelSearchable,
]) {
/** /**
* Table name * Table name
*/ */
@@ -30,13 +25,6 @@ export default class TaxRate extends mixin(TenantModel, [
return ['createdAt', 'updatedAt']; return ['createdAt', 'updatedAt'];
} }
/**
* Retrieves the tax rate meta.
*/
static get meta() {
return TaxRateMeta;
}
/** /**
* Virtual attributes. * Virtual attributes.
*/ */

View File

@@ -11,17 +11,9 @@ export default class UncategorizedCashflowTransaction extends mixin(
[ModelSettings] [ModelSettings]
) { ) {
id!: number; id!: number;
date!: Date | string;
amount!: number; amount!: number;
categorized!: boolean; categorized!: boolean;
accountId!: number; accountId!: number;
referenceNo!: string;
payee!: string;
description!: string;
plaidTransactionId!: string;
recognizedTransactionId!: number;
excludedAt: Date;
pending: boolean;
/** /**
* Table name. * Table name.
@@ -33,7 +25,7 @@ export default class UncategorizedCashflowTransaction extends mixin(
/** /**
* Timestamps columns. * Timestamps columns.
*/ */
get timestamps() { static get timestamps() {
return ['createdAt', 'updatedAt']; return ['createdAt', 'updatedAt'];
} }
@@ -46,9 +38,6 @@ export default class UncategorizedCashflowTransaction extends mixin(
'deposit', 'deposit',
'isDepositTransaction', 'isDepositTransaction',
'isWithdrawalTransaction', 'isWithdrawalTransaction',
'isRecognized',
'isExcluded',
'isPending',
]; ];
} }
@@ -86,99 +75,11 @@ export default class UncategorizedCashflowTransaction extends mixin(
return 0 < this.withdrawal; return 0 < this.withdrawal;
} }
/**
* Detarmines whether the transaction is recognized.
*/
public get isRecognized(): boolean {
return !!this.recognizedTransactionId;
}
/**
* Detarmines whether the transaction is excluded.
* @returns {boolean}
*/
public get isExcluded(): boolean {
return !!this.excludedAt;
}
/**
* Detarmines whether the transaction is pending.
* @returns {boolean}
*/
public get isPending(): boolean {
return !!this.pending;
}
/**
* Model modifiers.
*/
static get modifiers() {
return {
/**
* Filters the not excluded transactions.
*/
notExcluded(query) {
query.whereNull('excluded_at');
},
/**
* Filters the excluded transactions.
*/
excluded(query) {
query.whereNotNull('excluded_at');
},
/**
* Filter out the recognized transactions.
* @param query
*/
recognized(query) {
query.whereNotNull('recognizedTransactionId');
},
/**
* Filter out the not recognized transactions.
* @param query
*/
notRecognized(query) {
query.whereNull('recognizedTransactionId');
},
categorized(query) {
query.whereNotNull('categorizeRefType');
query.whereNotNull('categorizeRefId');
},
notCategorized(query) {
query.whereNull('categorizeRefType');
query.whereNull('categorizeRefId');
},
/**
* Filters the not pending transactions.
*/
notPending(query) {
query.where('pending', false);
},
/**
* Filters the pending transactions.
*/
pending(query) {
query.where('pending', true);
},
};
}
/** /**
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { static get relationMappings() {
const Account = require('models/Account'); const Account = require('models/Account');
const {
RecognizedBankTransaction,
} = require('models/RecognizedBankTransaction');
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
return { return {
/** /**
@@ -192,30 +93,58 @@ export default class UncategorizedCashflowTransaction extends mixin(
to: 'accounts.id', to: 'accounts.id',
}, },
}, },
/**
* Transaction may has association to recognized transaction.
*/
recognizedTransaction: {
relation: Model.HasOneRelation,
modelClass: RecognizedBankTransaction,
join: {
from: 'uncategorized_cashflow_transactions.recognizedTransactionId',
to: 'recognized_bank_transactions.id',
},
},
/**
* Uncategorized transaction may has association to matched transaction.
*/
matchedBankTransactions: {
relation: Model.HasManyRelation,
modelClass: MatchedBankTransaction,
join: {
from: 'uncategorized_cashflow_transactions.id',
to: 'matched_bank_transactions.uncategorizedTransactionId',
},
},
}; };
} }
/**
* Updates the count of uncategorized transactions for the associated account
* based on the specified operation.
* @param {QueryContext} queryContext - The query context for the transaction.
* @param {boolean} increment - Indicates whether to increment or decrement the count.
*/
private async updateUncategorizedTransactionCount(
queryContext: QueryContext,
increment: boolean,
amount: number = 1
) {
const operation = increment ? 'increment' : 'decrement';
await Account.query(queryContext.transaction)
.findById(this.accountId)
[operation]('uncategorized_transactions', amount);
}
/**
* Runs after insert.
* @param {QueryContext} queryContext
*/
public async $afterInsert(queryContext) {
await super.$afterInsert(queryContext);
await this.updateUncategorizedTransactionCount(queryContext, true);
}
/**
* Runs after update.
* @param {ModelOptions} opt
* @param {QueryContext} queryContext
*/
public async $afterUpdate(
opt: ModelOptions,
queryContext: QueryContext
): Promise<any> {
await super.$afterUpdate(opt, queryContext);
if (this.id && this.categorized) {
await this.updateUncategorizedTransactionCount(queryContext, false);
}
}
/**
* Runs after delete.
* @param {QueryContext} queryContext
*/
public async $afterDelete(queryContext: QueryContext) {
await super.$afterDelete(queryContext);
await this.updateUncategorizedTransactionCount(queryContext, false);
}
} }

View File

@@ -2,12 +2,7 @@ import { Account } from 'models';
import TenantRepository from '@/repositories/TenantRepository'; import TenantRepository from '@/repositories/TenantRepository';
import { IAccount } from '@/interfaces'; import { IAccount } from '@/interfaces';
import { Knex } from 'knex'; import { Knex } from 'knex';
import { import { TaxPayableAccount } from '@/database/seeds/data/accounts';
PrepardExpenses,
TaxPayableAccount,
UnearnedRevenueAccount,
} from '@/database/seeds/data/accounts';
import { TenantMetadata } from '@/system/models';
export default class AccountRepository extends TenantRepository { export default class AccountRepository extends TenantRepository {
/** /**
@@ -184,67 +179,4 @@ export default class AccountRepository extends TenantRepository {
} }
return result; return result;
}; };
/**
* Finds or creates the unearned revenue.
* @param {Record<string, string>} extraAttrs
* @param {Knex.Transaction} trx
* @returns
*/
public async findOrCreateUnearnedRevenue(
extraAttrs: Record<string, string> = {},
trx?: Knex.Transaction
) {
// Retrieves the given tenant metadata.
const tenantMeta = await TenantMetadata.query().findOne({
tenantId: this.tenantId,
});
const _extraAttrs = {
currencyCode: tenantMeta.baseCurrency,
...extraAttrs,
};
let result = await this.model
.query(trx)
.findOne({ slug: UnearnedRevenueAccount.slug, ..._extraAttrs });
if (!result) {
result = await this.model.query(trx).insertAndFetch({
...UnearnedRevenueAccount,
..._extraAttrs,
});
}
return result;
}
/**
* Finds or creates the prepard expenses account.
* @param {Record<string, string>} extraAttrs
* @param {Knex.Transaction} trx
* @returns
*/
public async findOrCreatePrepardExpenses(
extraAttrs: Record<string, string> = {},
trx?: Knex.Transaction
) {
// Retrieves the given tenant metadata.
const tenantMeta = await TenantMetadata.query().findOne({
tenantId: this.tenantId,
});
const _extraAttrs = {
currencyCode: tenantMeta.baseCurrency,
...extraAttrs,
};
let result = await this.model
.query(trx)
.findOne({ slug: PrepardExpenses.slug, ..._extraAttrs });
if (!result) {
result = await this.model.query(trx).insertAndFetch({
...PrepardExpenses,
..._extraAttrs,
});
}
return result;
}
} }

View File

@@ -4,17 +4,12 @@ import CachableRepository from './CachableRepository';
export default class TenantRepository extends CachableRepository { export default class TenantRepository extends CachableRepository {
repositoryName: string; repositoryName: string;
tenantId: number;
/** /**
* Constructor method. * Constructor method.
* @param {number} tenantId * @param {number} tenantId
*/ */
constructor(knex, cache, i18n) { constructor(knex, cache, i18n) {
super(knex, cache, i18n); super(knex, cache, i18n);
} }
}
setTenantId(tenantId: number) {
this.tenantId = tenantId;
}
}

View File

@@ -249,7 +249,6 @@ export default class Ledger implements ILedger {
transactionId: entry.referenceId, transactionId: entry.referenceId,
transactionType: entry.referenceType, transactionType: entry.referenceType,
transactionSubType: entry.transactionType,
transactionNumber: entry.transactionNumber, transactionNumber: entry.transactionNumber,
referenceNumber: entry.referenceNumber, referenceNumber: entry.referenceNumber,
@@ -263,8 +262,6 @@ export default class Ledger implements ILedger {
taxRateId: entry.taxRateId, taxRateId: entry.taxRateId,
taxRate: entry.taxRate, taxRate: entry.taxRate,
note: entry.note,
}; };
} }

View File

@@ -19,8 +19,6 @@ export const transformLedgerEntryToTransaction = (
referenceId: entry.transactionId, referenceId: entry.transactionId,
transactionNumber: entry.transactionNumber, transactionNumber: entry.transactionNumber,
transactionType: entry.transactionSubType,
referenceNumber: entry.referenceNumber, referenceNumber: entry.referenceNumber,
note: entry.note, note: entry.note,

View File

@@ -13,21 +13,7 @@ export class AccountTransformer extends Transformer {
* @returns {Array} * @returns {Array}
*/ */
public includeAttributes = (): string[] => { public includeAttributes = (): string[] => {
return [ return ['formattedAmount', 'flattenName', 'bankBalanceFormatted'];
'formattedAmount',
'flattenName',
'bankBalanceFormatted',
'lastFeedsUpdatedAtFormatted',
'isFeedsPaused',
];
};
/**
* Exclude attributes.
* @returns {string[]}
*/
public excludeAttributes = (): string[] => {
return ['plaidItem'];
}; };
/** /**
@@ -66,24 +52,6 @@ export class AccountTransformer extends Transformer {
}); });
}; };
/**
* Retrieves the formatted last feeds update at.
* @param {IAccount} account
* @returns {string}
*/
protected lastFeedsUpdatedAtFormatted = (account: IAccount): string => {
return this.formatDate(account.lastFeedsUpdatedAt);
};
/**
* Detarmines whether the bank account connection is paused.
* @param account
* @returns {boolean}
*/
protected isFeedsPaused = (account: any): boolean => {
return account.plaidItem?.isPaused || false;
};
/** /**
* Transformes the accounts collection to flat or nested array. * Transformes the accounts collection to flat or nested array.
* @param {IAccount[]} * @param {IAccount[]}

Some files were not shown because too many files have changed in this diff Show More