mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
Compare commits
192 Commits
BIG-213
...
multi-line
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14d1f0bd1d | ||
|
|
82f8648c59 | ||
|
|
c928940d32 | ||
|
|
0a78d56015 | ||
|
|
1a5716873e | ||
|
|
01b7c86ab9 | ||
|
|
0ca209b195 | ||
|
|
be6f6e3c73 | ||
|
|
cb016be78c | ||
|
|
fc085f2328 | ||
|
|
9a34f3e283 | ||
|
|
7054e862d5 | ||
|
|
faa81abee4 | ||
|
|
6d01f2a323 | ||
|
|
72678bb936 | ||
|
|
9ae5644af9 | ||
|
|
e8830c5911 | ||
|
|
7699889bd6 | ||
|
|
35a061d188 | ||
|
|
c7c021c969 | ||
|
|
be8352654e | ||
|
|
fb58ab8cc1 | ||
|
|
8da89ebe8b | ||
|
|
d43d46ebec | ||
|
|
ac3a514795 | ||
|
|
f67c63a4fa | ||
|
|
0025dcf8d4 | ||
|
|
81995dc94f | ||
|
|
3fcc70c1d8 | ||
|
|
a986c7a250 | ||
|
|
37e25a8061 | ||
|
|
cfba628465 | ||
|
|
3d200f4d7d | ||
|
|
f21b01b1d6 | ||
|
|
3cbcfac333 | ||
|
|
cc21e1856f | ||
|
|
efd0e1e225 | ||
|
|
521b083ed7 | ||
|
|
a09fe26df7 | ||
|
|
c7a85c4cf8 | ||
|
|
f6350d3d61 | ||
|
|
64c0732e5f | ||
|
|
8e99a31455 | ||
|
|
6d0d0689e1 | ||
|
|
9836129e49 | ||
|
|
86631ea8c3 | ||
|
|
475ccd4903 | ||
|
|
8608144ec1 | ||
|
|
f9cf6d325a | ||
|
|
fc0240c692 | ||
|
|
b84675325f | ||
|
|
647bed5c67 | ||
|
|
00f5bb1d73 | ||
|
|
208800b411 | ||
|
|
5e12a4cea4 | ||
|
|
fdf3e34f1c | ||
|
|
d74337fb94 | ||
|
|
8cab012324 | ||
|
|
940b4f9175 | ||
|
|
5d0dd1fe3f | ||
|
|
ded4e2bb59 | ||
|
|
219e6fb466 | ||
|
|
7147e230de | ||
|
|
5ce11f192f | ||
|
|
71e865e9b7 | ||
|
|
590506f183 | ||
|
|
bed281a637 | ||
|
|
47dd767b3a | ||
|
|
8623b69991 | ||
|
|
a1ddc81dac | ||
|
|
832cdacebf | ||
|
|
9f979080b6 | ||
|
|
7f7301b31e | ||
|
|
6affbedef4 | ||
|
|
ba7f32c1bf | ||
|
|
305ce29ebb | ||
|
|
4cd0405078 | ||
|
|
783102449f | ||
|
|
ae617b2e1d | ||
|
|
9090d0a7b2 | ||
|
|
ffc55fa81b | ||
|
|
07c57ed539 | ||
|
|
788150f80d | ||
|
|
c4e77e4e3b | ||
|
|
c09384e49b | ||
|
|
4490c2d4b4 | ||
|
|
e11f1a95f6 | ||
|
|
b91273eee4 | ||
|
|
b5d570417b | ||
|
|
acd3265e35 | ||
|
|
894c899847 | ||
|
|
f6d4ec504f | ||
|
|
1a01461f5d | ||
|
|
f5e18fc1fe | ||
|
|
f64cd32985 | ||
|
|
89552d7ee2 | ||
|
|
4345623ea9 | ||
|
|
f457759e39 | ||
|
|
14d5e82b4a | ||
|
|
333b6f5a4b | ||
|
|
1660df20af | ||
|
|
14a9c4ba28 | ||
|
|
383be111fa | ||
|
|
7720b1cc34 | ||
|
|
db634cbb79 | ||
|
|
53f37f4f48 | ||
|
|
0a7b522b87 | ||
|
|
9e6500ac79 | ||
|
|
b93cb546f4 | ||
|
|
6d17f9cbeb | ||
|
|
998e6de211 | ||
|
|
6fb02f9869 | ||
|
|
449390143d | ||
|
|
51471ed000 | ||
|
|
fe214b1b2d | ||
|
|
6b6b73b77c | ||
|
|
c2815afbe3 | ||
|
|
fa7e6b1fca | ||
|
|
107a6f793b | ||
|
|
67d155759e | ||
|
|
7e2e87256f | ||
|
|
df7790d7c1 | ||
|
|
72128a72c4 | ||
|
|
eb3f23554f | ||
|
|
69ddf43b3e | ||
|
|
249eadaeaa | ||
|
|
59168bc691 | ||
|
|
81b26c6f13 | ||
|
|
da435d85d9 | ||
|
|
533006b90e | ||
|
|
d096e49d45 | ||
|
|
73acdb6240 | ||
|
|
38d4122d11 | ||
|
|
24a77c81b3 | ||
|
|
7f41b4280e | ||
|
|
aa89653967 | ||
|
|
b80bc95fa5 | ||
|
|
9a5befbee7 | ||
|
|
b7487f19d3 | ||
|
|
cd9039fe16 | ||
|
|
87f60f7461 | ||
|
|
202179ec0b | ||
|
|
168883a933 | ||
|
|
f62ec83e29 | ||
|
|
eff8b41720 | ||
|
|
632cc3d72e | ||
|
|
aefdaac68d | ||
|
|
b8a0a5509d | ||
|
|
a5eb42edaf | ||
|
|
67b519db61 | ||
|
|
91730d204e | ||
|
|
8a09de9771 | ||
|
|
50861940a8 | ||
|
|
55caf037cd | ||
|
|
c95eec565d | ||
|
|
79616cf1eb | ||
|
|
c27458ebcc | ||
|
|
da0fab9a58 | ||
|
|
5bbcb7913d | ||
|
|
48ff93b6ab | ||
|
|
f816e7f25c | ||
|
|
3cd66ba4d6 | ||
|
|
5d5d4a1972 | ||
|
|
b01528c06b | ||
|
|
cb1f587637 | ||
|
|
978ce6c441 | ||
|
|
fab22c9820 | ||
|
|
7edf268e75 | ||
|
|
87bf29f28c | ||
|
|
d305c7ad32 | ||
|
|
7a9c7209bc | ||
|
|
d2d37820f5 | ||
|
|
1889969191 | ||
|
|
8c2888fcd8 | ||
|
|
47879d04b2 | ||
|
|
f1f52ce972 | ||
|
|
dad8aeaff1 | ||
|
|
5aae45c8a8 | ||
|
|
3e437a041c | ||
|
|
5dde7f5584 | ||
|
|
8e0911ec85 | ||
|
|
66d2d6a612 | ||
|
|
8dc2b18707 | ||
|
|
589b29bbdd | ||
|
|
ca403872b3 | ||
|
|
738a84bb4b | ||
|
|
b37002bea6 | ||
|
|
b6deb842ff | ||
|
|
d3230767dd | ||
|
|
6c4b0cdac5 | ||
|
|
0b5cee070a | ||
|
|
906835c396 |
@@ -132,6 +132,24 @@
|
|||||||
"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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
|||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -126,6 +126,10 @@ 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>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|||||||
@@ -103,24 +103,20 @@ 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 })
|
||||||
@@ -136,23 +132,19 @@ 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 })
|
||||||
|
|||||||
@@ -250,10 +250,12 @@ 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 });
|
||||||
|
|||||||
@@ -90,27 +90,23 @@ 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 }),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -150,7 +146,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().escape()];
|
return [check('email').exists().isEmail().trim()];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -158,7 +154,11 @@ export default class AuthenticationController extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
private async login(req: Request, res: Response, next: Function): Response {
|
private async login(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: Function
|
||||||
|
): Promise<Response | null> {
|
||||||
const userDTO: ILoginDTO = this.matchedBodyData(req);
|
const userDTO: ILoginDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,17 +2,38 @@ 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.
|
||||||
*/
|
*/
|
||||||
router() {
|
public 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
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', 'contains', 'not_contain'])
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ 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 {
|
||||||
@@ -14,6 +15,7 @@ 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());
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -67,22 +66,4 @@ 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,37 @@
|
|||||||
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 } from 'express-validator';
|
import { param, query } 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),
|
||||||
@@ -35,7 +49,7 @@ export default class GetCashflowAccounts extends BaseController {
|
|||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
private getCashflowTransaction = async (
|
private getCashflowTransaction = async (
|
||||||
req: Request,
|
req: Request<{ transactionId: number }>,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
) => {
|
) => {
|
||||||
@@ -47,7 +61,6 @@ 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),
|
||||||
});
|
});
|
||||||
@@ -56,6 +69,39 @@ 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.
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { ValidationChain, check, param, query } from 'express-validator';
|
import { ValidationChain, body, 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 { AbilitySubject, CashflowAction } from '@/interfaces';
|
import {
|
||||||
|
AbilitySubject,
|
||||||
|
CashflowAction,
|
||||||
|
ICategorizeCashflowTransactioDTO,
|
||||||
|
} from '@/interfaces';
|
||||||
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
|
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -38,13 +43,23 @@ 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/:id/categorize',
|
'/transactions/categorize',
|
||||||
this.categorizeCashflowTransactionValidationSchema,
|
this.categorizeCashflowTransactionValidationSchema,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
this.categorizeCashflowTransaction,
|
this.categorizeCashflowTransaction,
|
||||||
@@ -89,6 +104,7 @@ 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(),
|
||||||
@@ -106,12 +122,11 @@ 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().escape(),
|
check('reference_no').optional({ nullable: true }).trim(),
|
||||||
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(),
|
||||||
@@ -161,7 +176,7 @@ export default class NewCashflowTransactionController extends BaseController {
|
|||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
private revertCategorizedCashflowTransaction = async (
|
private revertCategorizedCashflowTransaction = async (
|
||||||
req: Request,
|
req: Request<{ id: number }>,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
) => {
|
) => {
|
||||||
@@ -179,6 +194,34 @@ 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
|
||||||
@@ -191,14 +234,19 @@ export default class NewCashflowTransactionController extends BaseController {
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
) => {
|
) => {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: cashflowTransactionId } = req.params;
|
const matchedObject = this.matchedBodyData(req);
|
||||||
const cashflowTransaction = this.matchedBodyData(req);
|
const categorizeDTO = omit(matchedObject, [
|
||||||
|
'uncategorizedTransactionIds',
|
||||||
|
]) as ICategorizeCashflowTransactioDTO;
|
||||||
|
|
||||||
|
const uncategorizedTransactionIds =
|
||||||
|
matchedObject.uncategorizedTransactionIds;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.cashflowApplication.categorizeTransaction(
|
await this.cashflowApplication.categorizeTransaction(
|
||||||
tenantId,
|
tenantId,
|
||||||
cashflowTransactionId,
|
uncategorizedTransactionIds,
|
||||||
cashflowTransaction
|
categorizeDTO
|
||||||
);
|
);
|
||||||
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.',
|
||||||
@@ -269,7 +317,7 @@ export default class NewCashflowTransactionController extends BaseController {
|
|||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
public getUncategorizedCashflowTransactions = async (
|
public getUncategorizedCashflowTransactions = async (
|
||||||
req: Request,
|
req: Request<{ id: number }>,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
) => {
|
) => {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export default class ContactsController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
get autocompleteQuerySchema() {
|
get autocompleteQuerySchema() {
|
||||||
return [
|
return [
|
||||||
query('column_sort_by').optional().trim().escape(),
|
query('column_sort_by').optional().trim(),
|
||||||
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,32 +122,27 @@ 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')
|
||||||
@@ -165,120 +160,101 @@ 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(),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -106,11 +106,7 @@ export default class CustomersController extends ContactsController {
|
|||||||
*/
|
*/
|
||||||
get customerDTOSchema() {
|
get customerDTOSchema() {
|
||||||
return [
|
return [
|
||||||
check('customer_type')
|
check('customer_type').exists().isIn(['business', 'individual']).trim(),
|
||||||
.exists()
|
|
||||||
.isIn(['business', 'individual'])
|
|
||||||
.trim()
|
|
||||||
.escape(),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +119,6 @@ export default class CustomersController extends ContactsController {
|
|||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isString()
|
.isString()
|
||||||
.trim()
|
.trim()
|
||||||
.escape()
|
|
||||||
.isLength({ max: 3 }),
|
.isLength({ max: 3 }),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -133,7 +128,7 @@ export default class CustomersController extends ContactsController {
|
|||||||
*/
|
*/
|
||||||
get validateListQuerySchema() {
|
get validateListQuerySchema() {
|
||||||
return [
|
return [
|
||||||
query('column_sort_by').optional().trim().escape(),
|
query('column_sort_by').optional().trim(),
|
||||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||||
|
|
||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
|
|||||||
@@ -106,7 +106,6 @@ 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 }),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export default class CurrenciesController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get currencyParamSchema(): ValidationChain[] {
|
get currencyParamSchema(): ValidationChain[] {
|
||||||
return [param('currency_code').exists().trim().escape()];
|
return [param('currency_code').exists().trim()];
|
||||||
}
|
}
|
||||||
|
|
||||||
get listSchema(): ValidationChain[] {
|
get listSchema(): ValidationChain[] {
|
||||||
@@ -187,11 +187,13 @@ 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') {
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ 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')
|
||||||
@@ -123,7 +122,6 @@ 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')
|
||||||
@@ -144,7 +142,6 @@ 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')
|
||||||
@@ -179,7 +176,6 @@ 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')
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
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')
|
||||||
@@ -19,8 +17,7 @@ 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(),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,8 +51,7 @@ 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(),
|
||||||
|
|
||||||
|
|||||||
@@ -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().escape(),
|
query('transaction_type').optional().trim(),
|
||||||
query('transaction_id').optional().isInt().toInt(),
|
query('transaction_id').optional().isInt().toInt(),
|
||||||
oneOf(
|
oneOf(
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -40,8 +40,7 @@ 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(),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export default class InventoryAdjustmentsController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
get validateListQuerySchema() {
|
get validateListQuerySchema() {
|
||||||
return [
|
return [
|
||||||
query('column_sort_by').optional().trim().escape(),
|
query('column_sort_by').optional().trim(),
|
||||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||||
|
|
||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default class InviteUsersController extends BaseController {
|
|||||||
router.post(
|
router.post(
|
||||||
'/send',
|
'/send',
|
||||||
[
|
[
|
||||||
body('email').exists().trim().escape(),
|
body('email').exists().trim(),
|
||||||
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().escape()],
|
[param('token').exists().trim()],
|
||||||
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().escape(),
|
check('first_name').exists().trim(),
|
||||||
check('last_name').exists().trim().escape(),
|
check('last_name').exists().trim(),
|
||||||
check('password').exists().trim().escape().isLength({ min: 5 }),
|
check('password').exists().trim().isLength({ min: 5 }),
|
||||||
param('token').exists().trim().escape(),
|
param('token').exists().trim(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,13 +73,11 @@ 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 })
|
||||||
@@ -101,9 +99,8 @@ export default class ItemsCategoriesController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
get categoriesListValidationSchema() {
|
get categoriesListValidationSchema() {
|
||||||
return [
|
return [
|
||||||
query('column_sort_by').optional().trim().escape(),
|
query('column_sort_by').optional().trim(),
|
||||||
query('sort_order').optional().trim().escape().isIn(['desc', 'asc']),
|
query('sort_order').optional().trim().isIn(['desc', 'asc']),
|
||||||
|
|
||||||
query('stringified_filter_roles').optional().isJSON(),
|
query('stringified_filter_roles').optional().isJSON(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -207,10 +204,8 @@ export default class ItemsCategoriesController extends BaseController {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const { itemCategories, filterMeta } =
|
||||||
itemCategories,
|
await this.itemCategoriesService.getItemCategoriesList(
|
||||||
filterMeta,
|
|
||||||
} = await this.itemCategoriesService.getItemCategoriesList(
|
|
||||||
tenantId,
|
tenantId,
|
||||||
itemCategoriesFilter,
|
itemCategoriesFilter,
|
||||||
user
|
user
|
||||||
|
|||||||
@@ -96,13 +96,11 @@ 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(),
|
||||||
@@ -141,13 +139,11 @@ 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')
|
||||||
@@ -162,7 +158,6 @@ 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(),
|
||||||
|
|
||||||
@@ -184,7 +179,7 @@ export default class ItemsController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
private get validateListQuerySchema() {
|
private get validateListQuerySchema() {
|
||||||
return [
|
return [
|
||||||
query('column_sort_by').optional().trim().escape(),
|
query('column_sort_by').optional().trim(),
|
||||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||||
|
|
||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
|
|||||||
@@ -94,25 +94,21 @@ 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(),
|
||||||
@@ -163,7 +159,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().escape(),
|
query('column_sort_by').optional().trim(),
|
||||||
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(),
|
||||||
|
|||||||
@@ -61,15 +61,14 @@ export default class MediaController extends BaseController {
|
|||||||
|
|
||||||
get uploadValidationSchema() {
|
get uploadValidationSchema() {
|
||||||
return [
|
return [
|
||||||
// check('attachment'),
|
check('model_name').optional().trim(),
|
||||||
check('model_name').optional().trim().escape(),
|
check('model_id').optional().isNumeric(),
|
||||||
check('model_id').optional().isNumeric().toInt(),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
get linkValidationSchema() {
|
get linkValidationSchema() {
|
||||||
return [
|
return [
|
||||||
check('model_name').exists().trim().escape(),
|
check('model_name').exists().trim(),
|
||||||
check('model_id').exists().isNumeric().toInt(),
|
check('model_id').exists().isNumeric().toInt(),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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().escape(),
|
check('industry').optional({ nullable: true }).isString().trim(),
|
||||||
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,11 +87,7 @@ export default class OrganizationController extends BaseController {
|
|||||||
private get updateOrganizationValidationSchema(): ValidationChain[] {
|
private get updateOrganizationValidationSchema(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
...this.commonOrganizationValidationSchema,
|
...this.commonOrganizationValidationSchema,
|
||||||
check('tax_number')
|
check('tax_number').optional({ nullable: true }).isString().trim(),
|
||||||
.optional({ nullable: true })
|
|
||||||
.isString()
|
|
||||||
.trim()
|
|
||||||
.escape(),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ export default class BillsController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
private get billValidationSchema() {
|
private get billValidationSchema() {
|
||||||
return [
|
return [
|
||||||
check('bill_number').exists().trim().escape(),
|
check('bill_number').exists().trim(),
|
||||||
check('reference_no').optional().trim().escape(),
|
check('reference_no').optional().trim(),
|
||||||
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().escape(),
|
check('note').optional().trim(),
|
||||||
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,10 +126,7 @@ export default class BillsController extends BaseController {
|
|||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
.toFloat(),
|
.toFloat(),
|
||||||
check('entries.*.description')
|
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||||
.optional({ nullable: true })
|
|
||||||
.trim()
|
|
||||||
.escape(),
|
|
||||||
check('entries.*.landed_cost')
|
check('entries.*.landed_cost')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isBoolean()
|
.isBoolean()
|
||||||
@@ -141,7 +138,6 @@ 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 })
|
||||||
@@ -158,8 +154,8 @@ export default class BillsController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
private get billEditValidationSchema() {
|
private get billEditValidationSchema() {
|
||||||
return [
|
return [
|
||||||
check('bill_number').optional().trim().escape(),
|
check('bill_number').optional().trim(),
|
||||||
check('reference_no').optional().trim().escape(),
|
check('reference_no').optional().trim(),
|
||||||
check('bill_date').exists().isISO8601(),
|
check('bill_date').exists().isISO8601(),
|
||||||
check('due_date').optional().isISO8601(),
|
check('due_date').optional().isISO8601(),
|
||||||
|
|
||||||
@@ -170,7 +166,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().escape(),
|
check('note').optional().trim(),
|
||||||
check('open').default(false).isBoolean().toBoolean(),
|
check('open').default(false).isBoolean().toBoolean(),
|
||||||
|
|
||||||
check('entries').isArray({ min: 1 }),
|
check('entries').isArray({ min: 1 }),
|
||||||
@@ -184,10 +180,7 @@ export default class BillsController extends BaseController {
|
|||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
.toFloat(),
|
.toFloat(),
|
||||||
check('entries.*.description')
|
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||||
.optional({ nullable: true })
|
|
||||||
.trim()
|
|
||||||
.escape(),
|
|
||||||
check('entries.*.landed_cost')
|
check('entries.*.landed_cost')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isBoolean()
|
.isBoolean()
|
||||||
@@ -222,8 +215,8 @@ export default class BillsController extends BaseController {
|
|||||||
|
|
||||||
private get dueBillsListingValidationSchema() {
|
private get dueBillsListingValidationSchema() {
|
||||||
return [
|
return [
|
||||||
query('vendor_id').optional().trim().escape(),
|
query('vendor_id').optional().trim(),
|
||||||
query('payment_made_id').optional().trim().escape(),
|
query('payment_made_id').optional().trim(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,14 +111,15 @@ 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().escape(),
|
check('payment_number').optional({ nullable: true }).trim(),
|
||||||
check('payment_date').exists(),
|
check('payment_date').exists(),
|
||||||
check('statement').optional().trim().escape(),
|
check('statement').optional().trim(),
|
||||||
check('reference').optional().trim().escape(),
|
check('reference').optional().trim(),
|
||||||
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
|
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
|
|
||||||
check('entries').exists().isArray({ min: 1 }),
|
check('entries').exists().isArray(),
|
||||||
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(),
|
||||||
|
|||||||
@@ -156,13 +156,10 @@ 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')
|
check('vendor_credit_number').optional({ nullable: true }).trim(),
|
||||||
.optional({ nullable: true })
|
check('reference_no').optional().trim(),
|
||||||
.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().escape(),
|
check('note').optional().trim(),
|
||||||
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(),
|
||||||
@@ -178,10 +175,7 @@ export default class VendorCreditController extends BaseController {
|
|||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
.toFloat(),
|
.toFloat(),
|
||||||
check('entries.*.description')
|
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||||
.optional({ nullable: true })
|
|
||||||
.trim()
|
|
||||||
.escape(),
|
|
||||||
check('entries.*.warehouse_id')
|
check('entries.*.warehouse_id')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
@@ -202,13 +196,10 @@ 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')
|
check('vendor_credit_number').optional({ nullable: true }).trim(),
|
||||||
.optional({ nullable: true })
|
check('reference_no').optional().trim(),
|
||||||
.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().escape(),
|
check('note').optional().trim(),
|
||||||
|
|
||||||
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(),
|
||||||
@@ -223,10 +214,7 @@ export default class VendorCreditController extends BaseController {
|
|||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
.toFloat(),
|
.toFloat(),
|
||||||
check('entries.*.description')
|
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||||
.optional({ nullable: true })
|
|
||||||
.trim()
|
|
||||||
.escape(),
|
|
||||||
check('entries.*.warehouse_id')
|
check('entries.*.warehouse_id')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ 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
|
||||||
);
|
);
|
||||||
@@ -48,9 +46,7 @@ export default class ResourceController extends BaseController {
|
|||||||
resourceModel
|
resourceModel
|
||||||
);
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
resource_meta: this.transfromToResponse(
|
resource_meta: this.transfromToResponse(resourceMeta),
|
||||||
resourceMeta,
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -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().escape(),
|
check('credit_note_number').optional({ nullable: true }).trim(),
|
||||||
check('note').optional().trim().escape(),
|
check('note').optional().trim(),
|
||||||
check('terms_conditions').optional().trim().escape(),
|
check('terms_conditions').optional().trim(),
|
||||||
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,10 +228,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
.toFloat(),
|
.toFloat(),
|
||||||
check('entries.*.description')
|
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||||
.optional({ nullable: true })
|
|
||||||
.trim()
|
|
||||||
.escape(),
|
|
||||||
check('entries.*.warehouse_id')
|
check('entries.*.warehouse_id')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
|
|||||||
@@ -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().escape(),
|
check('payment_receive_no').optional({ nullable: true }).trim(),
|
||||||
check('statement').optional().trim().escape(),
|
check('statement').optional().trim(),
|
||||||
|
|
||||||
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
|
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
|
|
||||||
check('entries').isArray({ min: 1 }),
|
check('entries').isArray({}),
|
||||||
|
|
||||||
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,7 +176,6 @@ 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(),
|
||||||
|
|||||||
@@ -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().escape(),
|
check('estimate_number').optional().trim(),
|
||||||
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,8 +170,7 @@ 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()
|
||||||
@@ -181,9 +180,9 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
.isNumeric()
|
.isNumeric()
|
||||||
.toInt(),
|
.toInt(),
|
||||||
|
|
||||||
check('note').optional().trim().escape(),
|
check('note').optional().trim(),
|
||||||
check('terms_conditions').optional().trim().escape(),
|
check('terms_conditions').optional().trim(),
|
||||||
check('send_to_email').optional().trim().escape(),
|
check('send_to_email').optional().trim(),
|
||||||
|
|
||||||
check('attachments').isArray().optional(),
|
check('attachments').isArray().optional(),
|
||||||
check('attachments.*.key').exists().isString(),
|
check('attachments.*.key').exists().isString(),
|
||||||
|
|||||||
@@ -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().escape(),
|
check('invoice_no').optional().trim(),
|
||||||
check('reference_no').optional().trim().escape(),
|
check('reference_no').optional().trim(),
|
||||||
check('delivered').default(false).isBoolean().toBoolean(),
|
check('delivered').default(false).isBoolean().toBoolean(),
|
||||||
|
|
||||||
check('invoice_message').optional().trim().escape(),
|
check('invoice_message').optional().trim(),
|
||||||
check('terms_conditions').optional().trim().escape(),
|
check('terms_conditions').optional().trim(),
|
||||||
|
|
||||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
||||||
|
|
||||||
@@ -226,12 +226,10 @@ 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 })
|
||||||
|
|||||||
@@ -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().escape(),
|
check('receipt_number').optional().trim(),
|
||||||
check('reference_no').optional().trim().escape(),
|
check('reference_no').optional().trim(),
|
||||||
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,14 +150,13 @@ 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().escape(),
|
check('receipt_message').optional().trim(),
|
||||||
check('statement').optional().trim().escape(),
|
check('statement').optional().trim(),
|
||||||
check('attachments').isArray().optional(),
|
check('attachments').isArray().optional(),
|
||||||
check('attachments.*.key').exists().isString(),
|
check('attachments.*.key').exists().isString(),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -52,10 +52,7 @@ 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 [
|
return [query('key').optional().trim(), query('group').optional().trim()];
|
||||||
query('key').optional().trim().escape(),
|
|
||||||
query('group').optional().trim().escape(),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ 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 {
|
||||||
@@ -17,6 +18,9 @@ export class SubscriptionController extends BaseController {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private lemonSqueezyService: LemonSqueezyService;
|
private lemonSqueezyService: LemonSqueezyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private subscriptionApp: SubscriptionApplication;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
*/
|
*/
|
||||||
@@ -33,6 +37,14 @@ 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;
|
||||||
@@ -85,4 +97,84 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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().escape()];
|
return [param('resource_model').exists().trim()];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -236,5 +236,13 @@ 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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
|
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 receive',
|
PaymentReceive: 'Payment received',
|
||||||
Bill: 'Bill',
|
Bill: 'Bill',
|
||||||
BillPayment: 'Payment made',
|
BillPayment: 'Payment made',
|
||||||
VendorOpeningBalance: 'Vendor opening balance',
|
VendorOpeningBalance: 'Vendor opening balance',
|
||||||
@@ -17,12 +26,10 @@ 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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
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');
|
||||||
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
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');
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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');
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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');
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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');
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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');
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
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
|
||||||
|
`),
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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');
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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');
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// 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();
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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');
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
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');
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -12,8 +12,7 @@ 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);
|
||||||
|
|||||||
@@ -9,6 +9,28 @@ 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',
|
||||||
@@ -323,4 +345,6 @@ export default [
|
|||||||
index: 1,
|
index: 1,
|
||||||
predefined: 0,
|
predefined: 0,
|
||||||
},
|
},
|
||||||
|
UnearnedRevenueAccount,
|
||||||
|
PrepardExpenses,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ 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 {}
|
||||||
@@ -37,6 +38,8 @@ 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 {
|
||||||
@@ -66,7 +69,9 @@ export interface IAccountTransaction {
|
|||||||
referenceId: number;
|
referenceId: number;
|
||||||
|
|
||||||
referenceNumber?: string;
|
referenceNumber?: string;
|
||||||
|
|
||||||
transactionNumber?: string;
|
transactionNumber?: string;
|
||||||
|
transactionType?: string;
|
||||||
|
|
||||||
note?: string;
|
note?: string;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
import {
|
import {
|
||||||
IFinancialSheetCommonMeta,
|
IFinancialSheetCommonMeta,
|
||||||
INumberFormatQuery,
|
INumberFormatQuery,
|
||||||
@@ -235,6 +236,7 @@ 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;
|
||||||
@@ -257,7 +259,6 @@ export interface IUncategorizedCashflowTransaction {
|
|||||||
categorized: boolean;
|
categorized: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface CreateUncategorizedTransactionDTO {
|
export interface CreateUncategorizedTransactionDTO {
|
||||||
date: Date | string;
|
date: Date | string;
|
||||||
accountId: number;
|
accountId: number;
|
||||||
@@ -267,4 +268,34 @@ 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,19 +130,23 @@ export interface ICommandCashflowDeletedPayload {
|
|||||||
|
|
||||||
export interface ICashflowTransactionCategorizedPayload {
|
export interface ICashflowTransactionCategorizedPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
cashflowTransactionId: number;
|
uncategorizedTransactions: Array<IUncategorizedCashflowTransaction>;
|
||||||
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;
|
||||||
uncategorizedTransaction: IUncategorizedCashflowTransaction;
|
uncategorizedTransactionId: number;
|
||||||
|
oldUncategorizedTransactions: Array<IUncategorizedCashflowTransaction>;
|
||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
export interface ICashflowTransactionUncategorizedPayload {
|
export interface ICashflowTransactionUncategorizedPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
uncategorizedTransaction: IUncategorizedCashflowTransaction;
|
uncategorizedTransactionId: number;
|
||||||
oldUncategorizedTransaction: IUncategorizedCashflowTransaction;
|
uncategorizedTransactions: Array<IUncategorizedCashflowTransaction>;
|
||||||
|
oldUncategorizedTransactions: Array<IUncategorizedCashflowTransaction>;
|
||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,3 +168,10 @@ export interface IGetUncategorizedTransactionsQuery {
|
|||||||
page?: number;
|
page?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IGetRecognizedTransactionsQuery {
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
accountId?: number;
|
||||||
|
}
|
||||||
@@ -29,4 +29,9 @@ export interface ICashflowAccountTransaction {
|
|||||||
|
|
||||||
date: Date;
|
date: Date;
|
||||||
formattedDate: string;
|
formattedDate: string;
|
||||||
|
|
||||||
|
status: string;
|
||||||
|
formattedStatus: string;
|
||||||
|
|
||||||
|
uncategorizedTransactionId: number;
|
||||||
}
|
}
|
||||||
|
|||||||
8
packages/server/src/interfaces/Import.ts
Normal file
8
packages/server/src/interfaces/Import.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { ImportFilePreviewPOJO } from "@/services/Import/interfaces";
|
||||||
|
|
||||||
|
|
||||||
|
export interface IImportFileCommitedEventPayload {
|
||||||
|
tenantId: number;
|
||||||
|
importId: number;
|
||||||
|
meta: ImportFilePreviewPOJO;
|
||||||
|
}
|
||||||
@@ -40,6 +40,8 @@ export interface ILedgerEntry {
|
|||||||
date: Date | string;
|
date: Date | string;
|
||||||
|
|
||||||
transactionType: string;
|
transactionType: string;
|
||||||
|
transactionSubType?: string;
|
||||||
|
|
||||||
transactionId: number;
|
transactionId: number;
|
||||||
|
|
||||||
transactionNumber?: string;
|
transactionNumber?: string;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
export interface IPlaidItemCreatedEventPayload {
|
export interface IPlaidItemCreatedEventPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
plaidAccessToken: string;
|
plaidAccessToken: string;
|
||||||
@@ -54,3 +56,10 @@ export interface SyncAccountsTransactionsTask {
|
|||||||
plaidAccountId: number;
|
plaidAccountId: number;
|
||||||
plaidTransactions: PlaidTransaction[];
|
plaidTransactions: PlaidTransaction[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IPlaidTransactionsSyncedEventPayload {
|
||||||
|
tenantId: number;
|
||||||
|
plaidAccountId: number;
|
||||||
|
batch: string;
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,69 +1,12 @@
|
|||||||
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 {
|
||||||
constructor() {
|
private static instance: PlaidClientWrapper;
|
||||||
|
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],
|
||||||
@@ -75,26 +18,13 @@ 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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allows us to log API request data for troubleshooting purposes.
|
public static getClient(): PlaidApi {
|
||||||
createWrappedClientMethod(clientMethod, log) {
|
if (!PlaidClientWrapper.instance) {
|
||||||
return async (...args) => {
|
PlaidClientWrapper.instance = new PlaidClientWrapper();
|
||||||
try {
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
};
|
return PlaidClientWrapper.instance.client;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ 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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -169,8 +169,8 @@ export class Transformer {
|
|||||||
* @param number
|
* @param number
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
protected formatNumber(number) {
|
protected formatNumber(number, props?) {
|
||||||
return formatNumber(number, { money: false });
|
return formatNumber(number, { money: false, ...props });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -102,6 +102,19 @@ 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();
|
||||||
@@ -246,5 +259,26 @@ 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
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ import { PaymentReceiveMailNotificationJob } from '@/services/Sales/PaymentRecei
|
|||||||
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);
|
||||||
@@ -29,6 +32,9 @@ export default ({ agenda }: { agenda: 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', {});
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ 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 = {
|
||||||
@@ -131,6 +135,10 @@ 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));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ 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 {
|
||||||
/**
|
/**
|
||||||
@@ -321,6 +322,18 @@ 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export default class AccountTransaction extends TenantModel {
|
|||||||
debit: number;
|
debit: number;
|
||||||
exchangeRate: number;
|
exchangeRate: number;
|
||||||
taxRate: number;
|
taxRate: number;
|
||||||
|
transactionType: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
@@ -53,7 +54,7 @@ export default class AccountTransaction extends TenantModel {
|
|||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
get referenceTypeFormatted() {
|
get referenceTypeFormatted() {
|
||||||
return getTransactionTypeLabel(this.referenceType);
|
return getTransactionTypeLabel(this.referenceType, this.transactionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
70
packages/server/src/models/BankRule.ts
Normal file
70
packages/server/src/models/BankRule.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
24
packages/server/src/models/BankRuleCondition.ts
Normal file
24
packages/server/src/models/BankRuleCondition.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
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 [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -404,6 +404,7 @@ 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: {
|
||||||
@@ -485,6 +486,21 @@ 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');
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,9 +525,9 @@ export default class Bill extends mixin(TenantModel, [
|
|||||||
return notFoundBillsIds;
|
return notFoundBillsIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
static changePaymentAmount(billId, amount) {
|
static changePaymentAmount(billId, amount, trx) {
|
||||||
const changeMethod = amount > 0 ? 'increment' : 'decrement';
|
const changeMethod = amount > 0 ? 'increment' : 'decrement';
|
||||||
return this.query()
|
return this.query(trx)
|
||||||
.where('id', billId)
|
.where('id', billId)
|
||||||
[changeMethod]('payment_amount', Math.abs(amount));
|
[changeMethod]('payment_amount', Math.abs(amount));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { getTransactionTypeLabel } from '@/utils/transactions-types';
|
import { getCashflowTransactionFormattedType } 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 getTransactionTypeLabel(this.transactionType);
|
return getCashflowTransactionFormattedType(this.transactionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
get typeMeta() {
|
get typeMeta() {
|
||||||
@@ -95,6 +95,34 @@ 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.
|
||||||
*/
|
*/
|
||||||
@@ -102,6 +130,7 @@ 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 {
|
||||||
/**
|
/**
|
||||||
@@ -130,8 +159,7 @@ export default class CashflowTransaction extends TenantModel {
|
|||||||
to: 'accounts_transactions.referenceId',
|
to: 'accounts_transactions.referenceId',
|
||||||
},
|
},
|
||||||
filter(builder) {
|
filter(builder) {
|
||||||
const referenceTypes = getCashflowAccountTransactionsTypes();
|
builder.where('reference_type', 'CashflowTransaction');
|
||||||
builder.whereIn('reference_type', referenceTypes);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -158,6 +186,22 @@ 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);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ 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: {
|
||||||
@@ -234,6 +235,21 @@ 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');
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ 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: {
|
||||||
@@ -140,6 +141,21 @@ 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');
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
32
packages/server/src/models/MatchedBankTransaction.ts
Normal file
32
packages/server/src/models/MatchedBankTransaction.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
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 {};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
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.
|
||||||
*/
|
*/
|
||||||
@@ -21,4 +23,19 @@ 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
72
packages/server/src/models/RecognizedBankTransaction.ts
Normal file
72
packages/server/src/models/RecognizedBankTransaction.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -411,6 +411,7 @@ 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 {
|
||||||
/**
|
/**
|
||||||
@@ -543,6 +544,21 @@ 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');
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
69
packages/server/src/models/TaxRate.settings.ts
Normal file
69
packages/server/src/models/TaxRate.settings.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -2,8 +2,13 @@ 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, [ModelSearchable]) {
|
export default class TaxRate extends mixin(TenantModel, [
|
||||||
|
ModelSetting,
|
||||||
|
ModelSearchable,
|
||||||
|
]) {
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
*/
|
*/
|
||||||
@@ -25,6 +30,13 @@ export default class TaxRate extends mixin(TenantModel, [ModelSearchable]) {
|
|||||||
return ['createdAt', 'updatedAt'];
|
return ['createdAt', 'updatedAt'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the tax rate meta.
|
||||||
|
*/
|
||||||
|
static get meta() {
|
||||||
|
return TaxRateMeta;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Virtual attributes.
|
* Virtual attributes.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,9 +11,17 @@ 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.
|
||||||
@@ -25,7 +33,7 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
|||||||
/**
|
/**
|
||||||
* Timestamps columns.
|
* Timestamps columns.
|
||||||
*/
|
*/
|
||||||
static get timestamps() {
|
get timestamps() {
|
||||||
return ['createdAt', 'updatedAt'];
|
return ['createdAt', 'updatedAt'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +46,9 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
|||||||
'deposit',
|
'deposit',
|
||||||
'isDepositTransaction',
|
'isDepositTransaction',
|
||||||
'isWithdrawalTransaction',
|
'isWithdrawalTransaction',
|
||||||
|
'isRecognized',
|
||||||
|
'isExcluded',
|
||||||
|
'isPending',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,11 +86,99 @@ 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 {
|
||||||
/**
|
/**
|
||||||
@@ -93,58 +192,30 @@ 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ 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 { TaxPayableAccount } from '@/database/seeds/data/accounts';
|
import {
|
||||||
|
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 {
|
||||||
/**
|
/**
|
||||||
@@ -179,4 +184,67 @@ 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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.
|
||||||
@@ -12,4 +13,8 @@ export default class TenantRepository extends CachableRepository {
|
|||||||
constructor(knex, cache, i18n) {
|
constructor(knex, cache, i18n) {
|
||||||
super(knex, cache, i18n);
|
super(knex, cache, i18n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTenantId(tenantId: number) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,8 @@ 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,
|
||||||
|
|||||||
@@ -13,7 +13,21 @@ export class AccountTransformer extends Transformer {
|
|||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
public includeAttributes = (): string[] => {
|
public includeAttributes = (): string[] => {
|
||||||
return ['formattedAmount', 'flattenName', 'bankBalanceFormatted'];
|
return [
|
||||||
|
'formattedAmount',
|
||||||
|
'flattenName',
|
||||||
|
'bankBalanceFormatted',
|
||||||
|
'lastFeedsUpdatedAtFormatted',
|
||||||
|
'isFeedsPaused',
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exclude attributes.
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
public excludeAttributes = (): string[] => {
|
||||||
|
return ['plaidItem'];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,6 +66,24 @@ 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[]}
|
||||||
|
|||||||
@@ -96,6 +96,11 @@ export class CreateAccount {
|
|||||||
...createAccountDTO,
|
...createAccountDTO,
|
||||||
slug: kebabCase(createAccountDTO.name),
|
slug: kebabCase(createAccountDTO.name),
|
||||||
currencyCode: createAccountDTO.currencyCode || baseCurrency,
|
currencyCode: createAccountDTO.currencyCode || baseCurrency,
|
||||||
|
|
||||||
|
// Mark the account is Plaid owner since Plaid item/account is defined on creating.
|
||||||
|
isSyncingOwner: Boolean(
|
||||||
|
createAccountDTO.plaidAccountId || createAccountDTO.plaidItemId
|
||||||
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -117,12 +122,7 @@ export class CreateAccount {
|
|||||||
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
||||||
|
|
||||||
// Authorize the account creation.
|
// Authorize the account creation.
|
||||||
await this.authorize(
|
await this.authorize(tenantId, accountDTO, tenantMeta.baseCurrency, params);
|
||||||
tenantId,
|
|
||||||
accountDTO,
|
|
||||||
tenantMeta.baseCurrency,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
// Transformes the DTO to model.
|
// Transformes the DTO to model.
|
||||||
const accountInputModel = this.transformDTOToModel(
|
const accountInputModel = this.transformDTOToModel(
|
||||||
accountDTO,
|
accountDTO,
|
||||||
@@ -157,4 +157,3 @@ export class CreateAccount {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ export class GetAccount {
|
|||||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
// Find the given account or throw not found error.
|
// Find the given account or throw not found error.
|
||||||
const account = await Account.query().findById(accountId).throwIfNotFound();
|
const account = await Account.query()
|
||||||
|
.findById(accountId)
|
||||||
|
.withGraphFetched('plaidItem')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||||
|
|
||||||
|
|||||||
@@ -96,10 +96,11 @@ export class AttachmentsApplication {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the presigned url of the given attachment key.
|
* Retrieves the presigned url of the given attachment key.
|
||||||
|
* @param {number} tenantId
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
*/
|
*/
|
||||||
public getPresignedUrl(key: string): Promise<string> {
|
public getPresignedUrl(tenantId: number, key: string): Promise<string> {
|
||||||
return this.getPresignedUrlService.getPresignedUrl(key);
|
return this.getPresignedUrlService.getPresignedUrl(tenantId, key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,34 @@
|
|||||||
import { Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { GetObjectCommand } from '@aws-sdk/client-s3';
|
import { GetObjectCommand } from '@aws-sdk/client-s3';
|
||||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||||
import { s3 } from '@/lib/S3/S3';
|
import { s3 } from '@/lib/S3/S3';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class getAttachmentPresignedUrl {
|
export class getAttachmentPresignedUrl {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the presigned url of the given attachment key.
|
* Retrieves the presigned url of the given attachment key with the original filename.
|
||||||
|
* @param {number} tenantId
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @returns {Promise<string?>}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
async getPresignedUrl(key: string) {
|
async getPresignedUrl(tenantId: number, key: string) {
|
||||||
|
const { Document } = this.tenancy.models(tenantId);
|
||||||
|
const foundDocument = await Document.query().findOne({ key });
|
||||||
|
|
||||||
|
let ResponseContentDisposition = 'attachment';
|
||||||
|
if (foundDocument && foundDocument.originName) {
|
||||||
|
ResponseContentDisposition += `; filename="${foundDocument.originName}"`;
|
||||||
|
}
|
||||||
|
|
||||||
const command = new GetObjectCommand({
|
const command = new GetObjectCommand({
|
||||||
Bucket: config.s3.bucket,
|
Bucket: config.s3.bucket,
|
||||||
Key: key,
|
Key: key,
|
||||||
|
ResponseContentDisposition,
|
||||||
});
|
});
|
||||||
const signedUrl = await getSignedUrl(s3, command, { expiresIn: 300 });
|
const signedUrl = await getSignedUrl(s3, command, { expiresIn: 300 });
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { DisconnectBankAccount } from './DisconnectBankAccount';
|
||||||
|
import { RefreshBankAccountService } from './RefreshBankAccount';
|
||||||
|
import { PauseBankAccountFeeds } from './PauseBankAccountFeeds';
|
||||||
|
import { ResumeBankAccountFeeds } from './ResumeBankAccountFeeds';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class BankAccountsApplication {
|
||||||
|
@Inject()
|
||||||
|
private disconnectBankAccountService: DisconnectBankAccount;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private refreshBankAccountService: RefreshBankAccountService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private resumeBankAccountFeedsService: ResumeBankAccountFeeds;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private pauseBankAccountFeedsService: PauseBankAccountFeeds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects the given bank account.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} bankAccountId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async disconnectBankAccount(tenantId: number, bankAccountId: number) {
|
||||||
|
return this.disconnectBankAccountService.disconnectBankAccount(
|
||||||
|
tenantId,
|
||||||
|
bankAccountId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the bank transactions of the given bank account.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} bankAccountId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async refreshBankAccount(tenantId: number, bankAccountId: number) {
|
||||||
|
return this.refreshBankAccountService.refreshBankAccount(
|
||||||
|
tenantId,
|
||||||
|
bankAccountId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pauses the feeds sync of the given bank account.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} bankAccountId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async pauseBankAccount(tenantId: number, bankAccountId: number) {
|
||||||
|
return this.pauseBankAccountFeedsService.pauseBankAccountFeeds(
|
||||||
|
tenantId,
|
||||||
|
bankAccountId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resumes the feeds sync of the given bank account.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} bankAccountId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async resumeBankAccount(tenantId: number, bankAccountId: number) {
|
||||||
|
return this.resumeBankAccountFeedsService.resumeBankAccountFeeds(
|
||||||
|
tenantId,
|
||||||
|
bankAccountId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import { PlaidClientWrapper } from '@/lib/Plaid';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import {
|
||||||
|
ERRORS,
|
||||||
|
IBankAccountDisconnectedEventPayload,
|
||||||
|
IBankAccountDisconnectingEventPayload,
|
||||||
|
} from './types';
|
||||||
|
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class DisconnectBankAccount {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects the given bank account.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} bankAccountId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async disconnectBankAccount(tenantId: number, bankAccountId: number) {
|
||||||
|
const { Account, PlaidItem } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retrieve the bank account or throw not found error.
|
||||||
|
const account = await Account.query()
|
||||||
|
.findById(bankAccountId)
|
||||||
|
.whereIn('account_type', [ACCOUNT_TYPE.CASH, ACCOUNT_TYPE.BANK])
|
||||||
|
.withGraphFetched('plaidItem')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const oldPlaidItem = account.plaidItem;
|
||||||
|
|
||||||
|
if (!oldPlaidItem) {
|
||||||
|
throw new ServiceError(ERRORS.BANK_ACCOUNT_NOT_CONNECTED);
|
||||||
|
}
|
||||||
|
const plaidInstance = PlaidClientWrapper.getClient();
|
||||||
|
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
|
// Triggers `onBankAccountDisconnecting` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.bankAccount.onDisconnecting, {
|
||||||
|
tenantId,
|
||||||
|
bankAccountId,
|
||||||
|
} as IBankAccountDisconnectingEventPayload);
|
||||||
|
|
||||||
|
// Remove the Plaid item from the system.
|
||||||
|
await PlaidItem.query(trx).findById(account.plaidItemId).delete();
|
||||||
|
|
||||||
|
// Remove the plaid item association to the bank account.
|
||||||
|
await Account.query(trx).findById(bankAccountId).patch({
|
||||||
|
plaidAccountId: null,
|
||||||
|
plaidItemId: null,
|
||||||
|
isFeedsActive: false,
|
||||||
|
});
|
||||||
|
// Remove the Plaid item.
|
||||||
|
await plaidInstance.itemRemove({
|
||||||
|
access_token: oldPlaidItem.plaidAccessToken,
|
||||||
|
});
|
||||||
|
// Triggers `onBankAccountDisconnected` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.bankAccount.onDisconnected, {
|
||||||
|
tenantId,
|
||||||
|
bankAccountId,
|
||||||
|
trx,
|
||||||
|
} as IBankAccountDisconnectedEventPayload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user