Compare commits
241 Commits
fix-plaid-
...
v0.19.12
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c986585cd9 | ||
|
|
250f0a30ef | ||
|
|
84b5e1adc1 | ||
|
|
2ab28370db | ||
|
|
a88a525326 | ||
|
|
fce8e2c5a4 | ||
|
|
095608266c | ||
|
|
0ec8aaa330 | ||
|
|
9fcb3ef77d | ||
|
|
dc61c57daf | ||
|
|
af284f3f6d | ||
|
|
c43123db76 | ||
|
|
ebbcab3926 | ||
|
|
a235f573c0 | ||
|
|
84a0b8f495 | ||
|
|
b87321c897 | ||
|
|
fc6ebfea5c | ||
|
|
c9fe6d9b37 | ||
|
|
161d60393a | ||
|
|
79413fa85e | ||
|
|
58552c6c94 | ||
|
|
2072e35cfa | ||
|
|
1eaac9d691 | ||
|
|
a916e8a0cb | ||
|
|
a56f560036 | ||
|
|
0fb886936c | ||
|
|
670136916f | ||
|
|
768297f137 | ||
|
|
ef505a0a62 | ||
|
|
42d40620ec | ||
|
|
959ef7a691 | ||
|
|
60f03f534b | ||
|
|
e44ebb700a | ||
|
|
8e94c7a755 | ||
|
|
3a2ca36c07 | ||
|
|
88ece74c8a | ||
|
|
67a8610328 | ||
|
|
278d61ce61 | ||
|
|
e72d6ad6b8 | ||
|
|
bf66b31679 | ||
|
|
b4d426d2e8 | ||
|
|
7f7dd270e7 | ||
|
|
f6bad8fe30 | ||
|
|
820b363f79 | ||
|
|
07740a51ef | ||
|
|
d15fb6fe19 | ||
|
|
5749ccec81 | ||
|
|
4a99f6c0cf | ||
|
|
59f480f9d5 | ||
|
|
6cb9c919b5 | ||
|
|
1062b65b5b | ||
|
|
bf3a70cabd | ||
|
|
f46cd28f87 | ||
|
|
dffcfe50aa | ||
|
|
fac55efbc7 | ||
|
|
8b90ce5f6c | ||
|
|
705b8da053 | ||
|
|
4a05ccc692 | ||
|
|
3200d65d90 | ||
|
|
3f23038227 | ||
|
|
408c807fc2 | ||
|
|
d29079a8c5 | ||
|
|
cca596b4a9 | ||
|
|
b768f18294 | ||
|
|
fed620505d | ||
|
|
a008aea3f3 | ||
|
|
25297bc191 | ||
|
|
1989887b25 | ||
|
|
e4fb126d39 | ||
|
|
5fcb2d9cc9 | ||
|
|
06ea631732 | ||
|
|
2f21107a43 | ||
|
|
fb8118bea8 | ||
|
|
4ba1c0aa22 | ||
|
|
169f115fa0 | ||
|
|
61ab2b78d9 | ||
|
|
93732430fc | ||
|
|
0215206220 | ||
|
|
3c8956fedf | ||
|
|
4477ada1ad | ||
|
|
fde9ccc5ca | ||
|
|
bbbd96f159 | ||
|
|
9f4de8115f | ||
|
|
d9f241a2f8 | ||
|
|
ee96dc68cc | ||
|
|
b12f090d13 | ||
|
|
f6ce761a27 | ||
|
|
8c89e04f54 | ||
|
|
f97b127a69 | ||
|
|
5c1fa8f5cd | ||
|
|
64c4d7b5a4 | ||
|
|
ea2fad648b | ||
|
|
9b8b51cb91 | ||
|
|
09b7e74d65 | ||
|
|
7137e06d99 | ||
|
|
fc29b765f7 | ||
|
|
2946475f89 | ||
|
|
d2193fdac0 | ||
|
|
ec2b7e332e | ||
|
|
a3704df6dd | ||
|
|
c3c784e52c | ||
|
|
bbcf695a6c | ||
|
|
038d4dd5a7 | ||
|
|
961e4b99e8 | ||
|
|
9991eebaaf | ||
|
|
cd90fede54 | ||
|
|
a2d28648bd | ||
|
|
3097d05eda | ||
|
|
ff94d8d9b2 | ||
|
|
79cc09fad9 | ||
|
|
c1b29c3f23 | ||
|
|
cf4bb3007e | ||
|
|
193a86cf30 | ||
|
|
7a81f14eb2 | ||
|
|
14d1f0bd1d | ||
|
|
82f8648c59 | ||
|
|
c928940d32 | ||
|
|
0a78d56015 | ||
|
|
1a5716873e | ||
|
|
01b7c86ab9 | ||
|
|
0ca209b195 | ||
|
|
be6f6e3c73 | ||
|
|
cb016be78c | ||
|
|
fc085f2328 | ||
|
|
9a34f3e283 | ||
|
|
7054e862d5 | ||
|
|
faa81abee4 | ||
|
|
6d01f2a323 | ||
|
|
72678bb936 | ||
|
|
df8b68fda6 | ||
|
|
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 |
@@ -132,6 +132,33 @@
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "oleynikd",
|
||||
"name": "Denis",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3976868?v=4",
|
||||
"profile": "https://github.com/oleynikd",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mittalsam98",
|
||||
"name": "Sachin Mittal",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/42431274?v=4",
|
||||
"profile": "https://myself.vercel.app/",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Champetaman",
|
||||
"name": "Camilo Oviedo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/64604272?v=4",
|
||||
"profile": "https://www.camilooviedo.com/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -2,6 +2,35 @@
|
||||
|
||||
All notable changes to Bigcapital server-side will be in this file.
|
||||
|
||||
## [0.19.4] - 18-08-2024
|
||||
|
||||
* fix: Allow multi-lines to statements transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/594
|
||||
* feat: Add amount comparators to amount bank rule field by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/595
|
||||
* fix: Transaction type and description do not show in general ledger. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/596
|
||||
* fix: Refresh accounts and account transactions. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/597
|
||||
* fix: Typo payments made by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/598
|
||||
* fix: Typo categories list by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/600
|
||||
* fix: Autofill the quick created customer/vendor by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/601
|
||||
* fix: Remove views tabs from receipts list by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/602
|
||||
* fix: Typo payment receive messages by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/599
|
||||
* fix: Enhance Dropzone visual of accept and reject modes by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/603
|
||||
* fix: Matching bank transactions should create associate payment transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/606
|
||||
* fix: Change Dropzone title and subtitle by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/607
|
||||
* fix: Inconsistance page size of paginated data tables by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/604
|
||||
* fix: Database connection lost error by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/611
|
||||
* fix: Language typos by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/613
|
||||
* Fix: Correctly display Date, Published At, and Created At in ExpenseDrawerHeader by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/612
|
||||
* fix: Delete bank account with uncategorized transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/614
|
||||
* feat: activate/inactivate account from drawer details by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/615
|
||||
|
||||
## [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
|
||||
|
||||
* fix: Balance sheet and P/L nested accounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/501
|
||||
|
||||
@@ -126,6 +126,11 @@ 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://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>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/oleynikd"><img src="https://avatars.githubusercontent.com/u/3976868?v=4?s=100" width="100px;" alt="Denis"/><br /><sub><b>Denis</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aoleynikd" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://myself.vercel.app/"><img src="https://avatars.githubusercontent.com/u/42431274?v=4?s=100" width="100px;" alt="Sachin Mittal"/><br /><sub><b>Sachin Mittal</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Amittalsam98" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.camilooviedo.com/"><img src="https://avatars.githubusercontent.com/u/64604272?v=4?s=100" width="100px;" alt="Camilo Oviedo"/><br /><sub><b>Camilo Oviedo</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=Champetaman" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"agendash": "^3.1.0",
|
||||
"app-root-path": "^3.0.0",
|
||||
"async": "^3.2.0",
|
||||
"async-mutex": "^0.5.0",
|
||||
"axios": "^1.6.0",
|
||||
"babel-loader": "^9.1.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
@@ -69,9 +70,8 @@
|
||||
"is-my-json-valid": "^2.20.5",
|
||||
"js-money": "^0.6.3",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"knex": "^0.95.15",
|
||||
"knex": "^3.1.0",
|
||||
"knex-cleaner": "^1.3.0",
|
||||
"knex-db-manager": "^0.6.1",
|
||||
"libphonenumber-js": "^1.9.6",
|
||||
"lodash": "^4.17.15",
|
||||
"lru-cache": "^6.0.0",
|
||||
|
||||
@@ -103,24 +103,20 @@ export default class AccountsController extends BaseController {
|
||||
check('name')
|
||||
.exists()
|
||||
.isLength({ min: 3, max: DATATYPES_LENGTH.STRING })
|
||||
.trim()
|
||||
.escape(),
|
||||
.trim(),
|
||||
check('code')
|
||||
.optional({ nullable: true })
|
||||
.isLength({ min: 3, max: 6 })
|
||||
.trim()
|
||||
.escape(),
|
||||
.trim(),
|
||||
check('currency_code').optional(),
|
||||
check('account_type')
|
||||
.exists()
|
||||
.isLength({ min: 3, max: DATATYPES_LENGTH.STRING })
|
||||
.trim()
|
||||
.escape(),
|
||||
.trim(),
|
||||
check('description')
|
||||
.optional({ nullable: true })
|
||||
.isLength({ max: DATATYPES_LENGTH.TEXT })
|
||||
.trim()
|
||||
.escape(),
|
||||
.trim(),
|
||||
check('parent_account_id')
|
||||
.optional({ nullable: true })
|
||||
.isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 })
|
||||
@@ -136,23 +132,19 @@ export default class AccountsController extends BaseController {
|
||||
check('name')
|
||||
.exists()
|
||||
.isLength({ min: 3, max: DATATYPES_LENGTH.STRING })
|
||||
.trim()
|
||||
.escape(),
|
||||
.trim(),
|
||||
check('code')
|
||||
.optional({ nullable: true })
|
||||
.isLength({ min: 3, max: 6 })
|
||||
.trim()
|
||||
.escape(),
|
||||
.trim(),
|
||||
check('account_type')
|
||||
.exists()
|
||||
.isLength({ min: 3, max: DATATYPES_LENGTH.STRING })
|
||||
.trim()
|
||||
.escape(),
|
||||
.trim(),
|
||||
check('description')
|
||||
.optional({ nullable: true })
|
||||
.isLength({ max: DATATYPES_LENGTH.TEXT })
|
||||
.trim()
|
||||
.escape(),
|
||||
.trim(),
|
||||
check('parent_account_id')
|
||||
.optional({ nullable: true })
|
||||
.isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 })
|
||||
|
||||
@@ -250,10 +250,12 @@ export class AttachmentsController extends BaseController {
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<Response | void> {
|
||||
const { tenantId } = req;
|
||||
const { id: documentKey } = req.params;
|
||||
|
||||
try {
|
||||
const presignedUrl = await this.attachmentsApplication.getPresignedUrl(
|
||||
tenantId,
|
||||
documentKey
|
||||
);
|
||||
return res.status(200).send({ presignedUrl });
|
||||
|
||||
@@ -90,27 +90,23 @@ export default class AuthenticationController extends BaseController {
|
||||
.exists()
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('last_name')
|
||||
.exists()
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('email')
|
||||
.exists()
|
||||
.isString()
|
||||
.isEmail()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('password')
|
||||
.exists()
|
||||
.isString()
|
||||
.isLength({ min: 6 })
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
];
|
||||
}
|
||||
@@ -150,7 +146,7 @@ export default class AuthenticationController extends BaseController {
|
||||
* @returns {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 {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);
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
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 { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
|
||||
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.
|
||||
*/
|
||||
@@ -16,6 +24,33 @@ export class BankAccountsController extends BaseController {
|
||||
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;
|
||||
}
|
||||
@@ -46,4 +81,138 @@ export class BankAccountsController extends BaseController {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
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';
|
||||
import { body, param } from 'express-validator';
|
||||
import {
|
||||
GetMatchedTransactionsFilter,
|
||||
IMatchTransactionsDTO,
|
||||
} from '@/services/Banking/Matching/types';
|
||||
|
||||
@Service()
|
||||
export class BankTransactionsMatchingController extends BaseController {
|
||||
@@ -20,9 +16,17 @@ export class BankTransactionsMatchingController extends BaseController {
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
'/:transactionId',
|
||||
'/unmatch/:transactionId',
|
||||
[param('transactionId').exists()],
|
||||
this.validationResult,
|
||||
this.unmatchMatchedBankTransaction.bind(this)
|
||||
);
|
||||
router.post(
|
||||
'/match',
|
||||
[
|
||||
param('transactionId').exists(),
|
||||
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(),
|
||||
@@ -30,12 +34,6 @@ export class BankTransactionsMatchingController extends BaseController {
|
||||
this.validationResult,
|
||||
this.matchBankTransaction.bind(this)
|
||||
);
|
||||
router.post(
|
||||
'/unmatch/:transactionId',
|
||||
[param('transactionId').exists()],
|
||||
this.validationResult,
|
||||
this.unmatchMatchedBankTransaction.bind(this)
|
||||
);
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -50,21 +48,21 @@ export class BankTransactionsMatchingController extends BaseController {
|
||||
req: Request<{ transactionId: number }>,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
): Promise<Response | null> {
|
||||
const { tenantId } = req;
|
||||
const { transactionId } = req.params;
|
||||
const matchTransactionDTO = this.matchedBodyData(
|
||||
req
|
||||
) as IMatchTransactionsDTO;
|
||||
const bodyData = this.matchedBodyData(req);
|
||||
|
||||
const uncategorizedTransactions = bodyData?.uncategorizedTransactions;
|
||||
const matchedTransactions = bodyData?.matchedTransactions;
|
||||
|
||||
try {
|
||||
await this.bankTransactionsMatchingApp.matchTransaction(
|
||||
tenantId,
|
||||
transactionId,
|
||||
matchTransactionDTO
|
||||
uncategorizedTransactions,
|
||||
matchedTransactions
|
||||
);
|
||||
return res.status(200).send({
|
||||
id: transactionId,
|
||||
ids: uncategorizedTransactions,
|
||||
message: 'The bank transaction has been matched.',
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { BankingRulesController } from './BankingRulesController';
|
||||
import { BankTransactionsMatchingController } from './BankTransactionsMatchingController';
|
||||
import { RecognizedTransactionsController } from './RecognizedTransactionsController';
|
||||
import { BankAccountsController } from './BankAccountsController';
|
||||
import { BankingUncategorizedController } from './BankingUncategorizedController';
|
||||
|
||||
@Service()
|
||||
export class BankingController extends BaseController {
|
||||
@@ -29,6 +30,10 @@ export class BankingController extends BaseController {
|
||||
'/bank_accounts',
|
||||
Container.get(BankAccountsController).router()
|
||||
);
|
||||
router.use(
|
||||
'/categorize',
|
||||
Container.get(BankingUncategorizedController).router()
|
||||
);
|
||||
return router;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,17 +33,25 @@ export class BankingRulesController extends BaseController {
|
||||
body('conditions.*.field').exists().isIn(['description', 'amount']),
|
||||
body('conditions.*.comparator')
|
||||
.exists()
|
||||
.isIn(['equals', 'contains', 'not_contain'])
|
||||
.default('contain'),
|
||||
body('conditions.*.value').exists(),
|
||||
.isIn([
|
||||
'equals',
|
||||
'equal',
|
||||
'contains',
|
||||
'not_contain',
|
||||
'bigger',
|
||||
'bigger_or_equal',
|
||||
'smaller',
|
||||
'smaller_or_equal',
|
||||
])
|
||||
.default('contain')
|
||||
.trim(),
|
||||
body('conditions.*.value').exists().trim(),
|
||||
|
||||
// Assign
|
||||
body('assign_category').isString(),
|
||||
body('assign_account_id').isInt({ min: 0 }),
|
||||
body('assign_payee').isString().optional({ nullable: true }),
|
||||
body('assign_memo').isString().optional({ nullable: true }),
|
||||
|
||||
body('recognition').isBoolean().toBoolean().optional({ nullable: true }),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { param } from 'express-validator';
|
||||
import { NextFunction, Request, Response, Router, query } from 'express';
|
||||
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 {
|
||||
@@ -15,9 +16,21 @@ export class ExcludeBankTransactionsController extends BaseController {
|
||||
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()],
|
||||
[param('transactionId').exists().toInt()],
|
||||
this.validationResult,
|
||||
this.excludeBankTransaction.bind(this)
|
||||
);
|
||||
@@ -29,7 +42,15 @@ export class ExcludeBankTransactionsController extends BaseController {
|
||||
);
|
||||
router.get(
|
||||
'/excluded',
|
||||
[],
|
||||
[
|
||||
query('account_id').optional().isNumeric().toInt(),
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
query('page_size').optional().isNumeric().toInt(),
|
||||
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||
query('max_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||
],
|
||||
this.validationResult,
|
||||
this.getExcludedBankTransactions.bind(this)
|
||||
);
|
||||
@@ -94,6 +115,63 @@ export class ExcludeBankTransactionsController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -107,9 +185,8 @@ export class ExcludeBankTransactionsController extends BaseController {
|
||||
next: NextFunction
|
||||
): Promise<Response | void> {
|
||||
const { tenantId } = req;
|
||||
const filter = this.matchedBodyData(req);
|
||||
const filter = this.matchedQueryData(req);
|
||||
|
||||
console.log('123');
|
||||
try {
|
||||
const data =
|
||||
await this.excludeBankTransactionApp.getExcludedBankTransactions(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -14,7 +15,20 @@ export class RecognizedTransactionsController extends BaseController {
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.get('/', this.getRecognizedTransactions.bind(this));
|
||||
router.get(
|
||||
'/',
|
||||
[
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
query('page_size').optional().isNumeric().toInt(),
|
||||
query('account_id').optional().isNumeric().toInt(),
|
||||
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||
query('max_amount').optional({ nullable: true }).isFloat().isFloat(),
|
||||
],
|
||||
this.validationResult,
|
||||
this.getRecognizedTransactions.bind(this)
|
||||
);
|
||||
router.get(
|
||||
'/transactions/:uncategorizedTransactionId',
|
||||
this.getRecognizedTransaction.bind(this)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { param } from 'express-validator';
|
||||
import { param, query } from 'express-validator';
|
||||
import BaseController from '../BaseController';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||
@@ -24,7 +24,12 @@ export default class GetCashflowAccounts extends BaseController {
|
||||
const router = Router();
|
||||
|
||||
router.get(
|
||||
'/transactions/:transactionId/matches',
|
||||
'/transactions/matches',
|
||||
[
|
||||
query('uncategorizeTransactionsIds').exists().isArray({ min: 1 }),
|
||||
query('uncategorizeTransactionsIds.*').exists().isNumeric().toInt(),
|
||||
],
|
||||
this.validationResult,
|
||||
this.getMatchedTransactions.bind(this)
|
||||
);
|
||||
router.get(
|
||||
@@ -44,7 +49,7 @@ export default class GetCashflowAccounts extends BaseController {
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
private getCashflowTransaction = async (
|
||||
req: Request,
|
||||
req: Request<{ transactionId: number }>,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
@@ -71,19 +76,24 @@ export default class GetCashflowAccounts extends BaseController {
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
private async getMatchedTransactions(
|
||||
req: Request<{ transactionId: number }>,
|
||||
req: Request<
|
||||
{ transactionId: number },
|
||||
null,
|
||||
null,
|
||||
{ uncategorizeTransactionsIds: Array<number> }
|
||||
>,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const { transactionId } = req.params;
|
||||
const uncategorizeTransactionsIds = req.query.uncategorizeTransactionsIds;
|
||||
const filter = this.matchedQueryData(req) as GetMatchedTransactionsFilter;
|
||||
|
||||
try {
|
||||
const data =
|
||||
await this.bankTransactionsMatchingApp.getMatchedTransactions(
|
||||
tenantId,
|
||||
transactionId,
|
||||
uncategorizeTransactionsIds,
|
||||
filter
|
||||
);
|
||||
return res.status(200).send(data);
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
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 { omit } from 'lodash';
|
||||
import BaseController from '../BaseController';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||
import { AbilitySubject, CashflowAction } from '@/interfaces';
|
||||
import {
|
||||
AbilitySubject,
|
||||
CashflowAction,
|
||||
ICategorizeCashflowTransactioDTO,
|
||||
} from '@/interfaces';
|
||||
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
|
||||
|
||||
@Service()
|
||||
@@ -38,13 +43,23 @@ export default class NewCashflowTransactionController extends BaseController {
|
||||
this.asyncMiddleware(this.newCashflowTransaction),
|
||||
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(
|
||||
'/transactions/:id/uncategorize',
|
||||
this.revertCategorizedCashflowTransaction,
|
||||
this.catchServiceErrors
|
||||
);
|
||||
router.post(
|
||||
'/transactions/:id/categorize',
|
||||
'/transactions/categorize',
|
||||
this.categorizeCashflowTransactionValidationSchema,
|
||||
this.validationResult,
|
||||
this.categorizeCashflowTransaction,
|
||||
@@ -69,6 +84,10 @@ export default class NewCashflowTransactionController extends BaseController {
|
||||
param('id').exists().isNumeric().toInt(),
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
query('page_size').optional().isNumeric().toInt(),
|
||||
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
|
||||
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||
query('max_amount').optional({ nullable: true }).isFloat().toFloat(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -89,6 +108,7 @@ export default class NewCashflowTransactionController extends BaseController {
|
||||
*/
|
||||
public get categorizeCashflowTransactionValidationSchema() {
|
||||
return [
|
||||
check('uncategorized_transaction_ids').exists().isArray({ min: 1 }),
|
||||
check('date').exists().isISO8601().toDate(),
|
||||
check('credit_account_id').exists().isInt().toInt(),
|
||||
check('transaction_number').optional(),
|
||||
@@ -106,12 +126,11 @@ export default class NewCashflowTransactionController extends BaseController {
|
||||
public get newTransactionValidationSchema() {
|
||||
return [
|
||||
check('date').exists().isISO8601().toDate(),
|
||||
check('reference_no').optional({ nullable: true }).trim().escape(),
|
||||
check('reference_no').optional({ nullable: true }).trim(),
|
||||
check('description')
|
||||
.optional({ nullable: true })
|
||||
.isLength({ min: 3 })
|
||||
.trim()
|
||||
.escape(),
|
||||
.trim(),
|
||||
check('transaction_type').exists(),
|
||||
|
||||
check('amount').exists().isFloat().toFloat(),
|
||||
@@ -161,7 +180,7 @@ export default class NewCashflowTransactionController extends BaseController {
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
private revertCategorizedCashflowTransaction = async (
|
||||
req: Request,
|
||||
req: Request<{ id: number }>,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
@@ -179,6 +198,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.
|
||||
* @param {Request} req
|
||||
@@ -191,14 +238,19 @@ export default class NewCashflowTransactionController extends BaseController {
|
||||
next: NextFunction
|
||||
) => {
|
||||
const { tenantId } = req;
|
||||
const { id: cashflowTransactionId } = req.params;
|
||||
const cashflowTransaction = this.matchedBodyData(req);
|
||||
const matchedObject = this.matchedBodyData(req);
|
||||
const categorizeDTO = omit(matchedObject, [
|
||||
'uncategorizedTransactionIds',
|
||||
]) as ICategorizeCashflowTransactioDTO;
|
||||
|
||||
const uncategorizedTransactionIds =
|
||||
matchedObject.uncategorizedTransactionIds;
|
||||
|
||||
try {
|
||||
await this.cashflowApplication.categorizeTransaction(
|
||||
tenantId,
|
||||
cashflowTransactionId,
|
||||
cashflowTransaction
|
||||
uncategorizedTransactionIds,
|
||||
categorizeDTO
|
||||
);
|
||||
return res.status(200).send({
|
||||
message: 'The cashflow transaction has been created successfully.',
|
||||
@@ -269,7 +321,7 @@ export default class NewCashflowTransactionController extends BaseController {
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
public getUncategorizedCashflowTransactions = async (
|
||||
req: Request,
|
||||
req: Request<{ id: number }>,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
|
||||
@@ -56,7 +56,7 @@ export default class ContactsController extends BaseController {
|
||||
*/
|
||||
get autocompleteQuerySchema() {
|
||||
return [
|
||||
query('column_sort_by').optional().trim().escape(),
|
||||
query('column_sort_by').optional().trim(),
|
||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
@@ -122,32 +122,27 @@ export default class ContactsController extends BaseController {
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('first_name')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('last_name')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('company_name')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
|
||||
check('display_name')
|
||||
.exists()
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
|
||||
check('email')
|
||||
@@ -165,120 +160,101 @@ export default class ContactsController extends BaseController {
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('personal_phone')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
|
||||
check('billing_address_1')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('billing_address_2')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('billing_address_city')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('billing_address_country')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('billing_address_email')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.isEmail()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('billing_address_postcode')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('billing_address_phone')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('billing_address_state')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
|
||||
check('shipping_address_1')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('shipping_address_2')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('shipping_address_city')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('shipping_address_country')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('shipping_address_email')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.isEmail()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('shipping_address_postcode')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('shipping_address_phone')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('shipping_address_state')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
|
||||
check('note')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.TEXT }),
|
||||
check('active').optional().isBoolean().toBoolean(),
|
||||
];
|
||||
|
||||
@@ -106,11 +106,7 @@ export default class CustomersController extends ContactsController {
|
||||
*/
|
||||
get customerDTOSchema() {
|
||||
return [
|
||||
check('customer_type')
|
||||
.exists()
|
||||
.isIn(['business', 'individual'])
|
||||
.trim()
|
||||
.escape(),
|
||||
check('customer_type').exists().isIn(['business', 'individual']).trim(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -123,7 +119,6 @@ export default class CustomersController extends ContactsController {
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: 3 }),
|
||||
];
|
||||
}
|
||||
@@ -133,7 +128,7 @@ export default class CustomersController extends ContactsController {
|
||||
*/
|
||||
get validateListQuerySchema() {
|
||||
return [
|
||||
query('column_sort_by').optional().trim().escape(),
|
||||
query('column_sort_by').optional().trim(),
|
||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
|
||||
@@ -106,7 +106,6 @@ export default class VendorsController extends ContactsController {
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ min: 3, max: 3 }),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export default class CurrenciesController extends BaseController {
|
||||
}
|
||||
|
||||
get currencyParamSchema(): ValidationChain[] {
|
||||
return [param('currency_code').exists().trim().escape()];
|
||||
return [param('currency_code').exists().trim()];
|
||||
}
|
||||
|
||||
get listSchema(): ValidationChain[] {
|
||||
@@ -187,11 +187,13 @@ export default class CurrenciesController extends BaseController {
|
||||
}
|
||||
if (error.errorType === 'currency_code_exists') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{
|
||||
type: 'CURRENCY_CODE_EXISTS',
|
||||
message: 'The given currency code is already exists.',
|
||||
code: 200,
|
||||
}],
|
||||
errors: [
|
||||
{
|
||||
type: 'CURRENCY_CODE_EXISTS',
|
||||
message: 'The given currency code is already exists.',
|
||||
code: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'CANNOT_DELETE_BASE_CURRENCY') {
|
||||
|
||||
@@ -89,7 +89,6 @@ export class ExpensesController extends BaseController {
|
||||
check('reference_no')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('payment_date').exists().isISO8601().toDate(),
|
||||
check('payment_account_id')
|
||||
@@ -123,7 +122,6 @@ export class ExpensesController extends BaseController {
|
||||
check('categories.*.description')
|
||||
.optional()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('categories.*.landed_cost').optional().isBoolean().toBoolean(),
|
||||
check('categories.*.project_id')
|
||||
@@ -144,7 +142,6 @@ export class ExpensesController extends BaseController {
|
||||
check('reference_no')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('payment_date').exists().isISO8601().toDate(),
|
||||
check('payment_account_id')
|
||||
@@ -179,7 +176,6 @@ export class ExpensesController extends BaseController {
|
||||
check('categories.*.description')
|
||||
.optional()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('categories.*.landed_cost').optional().isBoolean().toBoolean(),
|
||||
check('categories.*.project_id')
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { query } from 'express-validator';
|
||||
import BaseController from "../BaseController";
|
||||
import BaseController from '../BaseController';
|
||||
|
||||
export default class BaseFinancialReportController extends BaseController {
|
||||
|
||||
|
||||
get sheetNumberFormatValidationSchema() {
|
||||
return [
|
||||
query('number_format.precision')
|
||||
@@ -19,8 +17,7 @@ export default class BaseFinancialReportController extends BaseController {
|
||||
query('number_format.negative_format')
|
||||
.optional()
|
||||
.isIn(['parentheses', 'mines'])
|
||||
.trim()
|
||||
.escape(),
|
||||
.trim(),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,7 @@ export default class InventoryDetailsController extends BaseController {
|
||||
query('number_format.negative_format')
|
||||
.optional()
|
||||
.isIn(['parentheses', 'mines'])
|
||||
.trim()
|
||||
.escape(),
|
||||
.trim(),
|
||||
query('from_date').optional(),
|
||||
query('to_date').optional(),
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export default class JournalSheetController extends BaseFinancialReportControlle
|
||||
return [
|
||||
query('from_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(),
|
||||
oneOf(
|
||||
[
|
||||
|
||||
@@ -40,8 +40,7 @@ export default class TransactionsByReferenceController extends BaseController {
|
||||
query('number_format.negative_format')
|
||||
.optional()
|
||||
.isIn(['parentheses', 'mines'])
|
||||
.trim()
|
||||
.escape(),
|
||||
.trim(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ export default class InventoryAdjustmentsController extends BaseController {
|
||||
*/
|
||||
get validateListQuerySchema() {
|
||||
return [
|
||||
query('column_sort_by').optional().trim().escape(),
|
||||
query('column_sort_by').optional().trim(),
|
||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
|
||||
@@ -25,7 +25,7 @@ export default class InviteUsersController extends BaseController {
|
||||
router.post(
|
||||
'/send',
|
||||
[
|
||||
body('email').exists().trim().escape(),
|
||||
body('email').exists().trim(),
|
||||
body('role_id').exists().isNumeric().toInt(),
|
||||
],
|
||||
this.validationResult,
|
||||
@@ -57,7 +57,7 @@ export default class InviteUsersController extends BaseController {
|
||||
);
|
||||
router.get(
|
||||
'/invited/:token',
|
||||
[param('token').exists().trim().escape()],
|
||||
[param('token').exists().trim()],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.invited.bind(this)),
|
||||
this.handleServicesError
|
||||
@@ -72,10 +72,10 @@ export default class InviteUsersController extends BaseController {
|
||||
*/
|
||||
private get inviteUserDTO() {
|
||||
return [
|
||||
check('first_name').exists().trim().escape(),
|
||||
check('last_name').exists().trim().escape(),
|
||||
check('password').exists().trim().escape().isLength({ min: 5 }),
|
||||
param('token').exists().trim().escape(),
|
||||
check('first_name').exists().trim(),
|
||||
check('last_name').exists().trim(),
|
||||
check('password').exists().trim().isLength({ min: 5 }),
|
||||
param('token').exists().trim(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -73,13 +73,11 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
check('name')
|
||||
.exists()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ min: 0, max: DATATYPES_LENGTH.STRING }),
|
||||
check('description')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.TEXT }),
|
||||
check('sell_account_id')
|
||||
.optional({ nullable: true })
|
||||
@@ -101,9 +99,8 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
*/
|
||||
get categoriesListValidationSchema() {
|
||||
return [
|
||||
query('column_sort_by').optional().trim().escape(),
|
||||
query('sort_order').optional().trim().escape().isIn(['desc', 'asc']),
|
||||
|
||||
query('column_sort_by').optional().trim(),
|
||||
query('sort_order').optional().trim().isIn(['desc', 'asc']),
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
];
|
||||
}
|
||||
@@ -207,14 +204,12 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
};
|
||||
|
||||
try {
|
||||
const {
|
||||
itemCategories,
|
||||
filterMeta,
|
||||
} = await this.itemCategoriesService.getItemCategoriesList(
|
||||
tenantId,
|
||||
itemCategoriesFilter,
|
||||
user
|
||||
);
|
||||
const { itemCategories, filterMeta } =
|
||||
await this.itemCategoriesService.getItemCategoriesList(
|
||||
tenantId,
|
||||
itemCategoriesFilter,
|
||||
user
|
||||
);
|
||||
return res.status(200).send({
|
||||
item_categories: itemCategories,
|
||||
filter_meta: this.transfromToResponse(filterMeta),
|
||||
|
||||
@@ -96,13 +96,11 @@ export default class ItemsController extends BaseController {
|
||||
.exists()
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isIn(['service', 'non-inventory', 'inventory']),
|
||||
check('code')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
// Purchase attributes.
|
||||
check('purchasable').optional().isBoolean().toBoolean(),
|
||||
@@ -141,13 +139,11 @@ export default class ItemsController extends BaseController {
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.TEXT }),
|
||||
check('purchase_description')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.TEXT }),
|
||||
check('sell_tax_rate_id').optional({ nullable: true }).isInt().toInt(),
|
||||
check('purchase_tax_rate_id')
|
||||
@@ -162,7 +158,6 @@ export default class ItemsController extends BaseController {
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.TEXT }),
|
||||
check('active').optional().isBoolean().toBoolean(),
|
||||
|
||||
@@ -184,7 +179,7 @@ export default class ItemsController extends BaseController {
|
||||
*/
|
||||
private get validateListQuerySchema() {
|
||||
return [
|
||||
query('column_sort_by').optional().trim().escape(),
|
||||
query('column_sort_by').optional().trim(),
|
||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
|
||||
@@ -94,25 +94,21 @@ export default class ManualJournalsController extends BaseController {
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('journal_type')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('reference')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||
check('description')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ max: DATATYPES_LENGTH.TEXT }),
|
||||
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||
check('publish').optional().isBoolean().toBoolean(),
|
||||
@@ -163,7 +159,7 @@ export default class ManualJournalsController extends BaseController {
|
||||
query('page_size').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('stringified_filter_roles').optional().isJSON(),
|
||||
|
||||
@@ -61,15 +61,14 @@ export default class MediaController extends BaseController {
|
||||
|
||||
get uploadValidationSchema() {
|
||||
return [
|
||||
// check('attachment'),
|
||||
check('model_name').optional().trim().escape(),
|
||||
check('model_id').optional().isNumeric().toInt(),
|
||||
check('model_name').optional().trim(),
|
||||
check('model_id').optional().isNumeric(),
|
||||
];
|
||||
}
|
||||
|
||||
get linkValidationSchema() {
|
||||
return [
|
||||
check('model_name').exists().trim().escape(),
|
||||
check('model_name').exists().trim(),
|
||||
check('model_id').exists().isNumeric().toInt(),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { body } from 'express-validator';
|
||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||
import BaseController from '@/api/controllers/BaseController';
|
||||
import { OneClickDemoApplication } from '@/services/OneClickDemo/OneClickDemoApplication';
|
||||
import config from '@/config';
|
||||
@Service()
|
||||
export class OneClickDemoController extends BaseController {
|
||||
@Inject()
|
||||
private oneClickDemoApp: OneClickDemoApplication;
|
||||
|
||||
/**
|
||||
* Router constructor method.
|
||||
*/
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
// Protects the endpoints if the feature is not enabled.
|
||||
const protectMiddleware = (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
// Add your protection logic here
|
||||
if (config.oneClickDemoAccounts) {
|
||||
next();
|
||||
} else {
|
||||
res.status(403).send({ message: 'Forbidden' });
|
||||
}
|
||||
};
|
||||
router.post(
|
||||
'/one_click',
|
||||
protectMiddleware,
|
||||
asyncMiddleware(this.oneClickDemo.bind(this))
|
||||
);
|
||||
router.post(
|
||||
'/one_click_signin',
|
||||
[body('demo_id').exists()],
|
||||
this.validationResult,
|
||||
protectMiddleware,
|
||||
asyncMiddleware(this.oneClickSignIn.bind(this))
|
||||
);
|
||||
return router;
|
||||
}
|
||||
|
||||
/**
|
||||
* One-click demo application.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {NextFunction} next -
|
||||
*/
|
||||
private async oneClickDemo(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const data = await this.oneClickDemoApp.createOneClick();
|
||||
|
||||
return res.status(200).send({
|
||||
data,
|
||||
message: 'The one-click demo has been created successfully.',
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign-in to one-click demo account.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
private async oneClickSignIn(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { demoId } = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
const data = await this.oneClickDemoApp.autoSignIn(demoId);
|
||||
|
||||
return res.status(200).send(data);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ export default class OrganizationController extends BaseController {
|
||||
private get commonOrganizationValidationSchema(): ValidationChain[] {
|
||||
return [
|
||||
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('base_currency').exists().isISO4217(),
|
||||
check('timezone').exists().isIn(moment.tz.names()),
|
||||
@@ -87,11 +87,7 @@ export default class OrganizationController extends BaseController {
|
||||
private get updateOrganizationValidationSchema(): ValidationChain[] {
|
||||
return [
|
||||
...this.commonOrganizationValidationSchema,
|
||||
check('tax_number')
|
||||
.optional({ nullable: true })
|
||||
.isString()
|
||||
.trim()
|
||||
.escape(),
|
||||
check('tax_number').optional({ nullable: true }).isString().trim(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -100,8 +100,8 @@ export default class BillsController extends BaseController {
|
||||
*/
|
||||
private get billValidationSchema() {
|
||||
return [
|
||||
check('bill_number').exists().trim().escape(),
|
||||
check('reference_no').optional().trim().escape(),
|
||||
check('bill_number').exists().trim(),
|
||||
check('reference_no').optional().trim(),
|
||||
check('bill_date').exists().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('project_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||
|
||||
check('note').optional().trim().escape(),
|
||||
check('note').optional().trim(),
|
||||
check('open').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 })
|
||||
.isNumeric()
|
||||
.toFloat(),
|
||||
check('entries.*.description')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape(),
|
||||
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||
check('entries.*.landed_cost')
|
||||
.optional({ nullable: true })
|
||||
.isBoolean()
|
||||
@@ -141,7 +138,6 @@ export default class BillsController extends BaseController {
|
||||
check('entries.*.tax_code')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape()
|
||||
.isString(),
|
||||
check('entries.*.tax_rate_id')
|
||||
.optional({ nullable: true })
|
||||
@@ -158,8 +154,8 @@ export default class BillsController extends BaseController {
|
||||
*/
|
||||
private get billEditValidationSchema() {
|
||||
return [
|
||||
check('bill_number').optional().trim().escape(),
|
||||
check('reference_no').optional().trim().escape(),
|
||||
check('bill_number').optional().trim(),
|
||||
check('reference_no').optional().trim(),
|
||||
check('bill_date').exists().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('project_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||
|
||||
check('note').optional().trim().escape(),
|
||||
check('note').optional().trim(),
|
||||
check('open').default(false).isBoolean().toBoolean(),
|
||||
|
||||
check('entries').isArray({ min: 1 }),
|
||||
@@ -184,10 +180,7 @@ export default class BillsController extends BaseController {
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toFloat(),
|
||||
check('entries.*.description')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape(),
|
||||
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||
check('entries.*.landed_cost')
|
||||
.optional({ nullable: true })
|
||||
.isBoolean()
|
||||
@@ -222,8 +215,8 @@ export default class BillsController extends BaseController {
|
||||
|
||||
private get dueBillsListingValidationSchema() {
|
||||
return [
|
||||
query('vendor_id').optional().trim().escape(),
|
||||
query('payment_made_id').optional().trim().escape(),
|
||||
query('vendor_id').optional().trim(),
|
||||
query('payment_made_id').optional().trim(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -111,14 +111,15 @@ export default class BillsPayments extends BaseController {
|
||||
check('vendor_id').exists().isNumeric().toInt(),
|
||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
||||
|
||||
check('amount').exists().isNumeric().toFloat(),
|
||||
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('statement').optional().trim().escape(),
|
||||
check('reference').optional().trim().escape(),
|
||||
check('statement').optional().trim(),
|
||||
check('reference').optional().trim(),
|
||||
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.*.bill_id').exists().isNumeric().toInt(),
|
||||
check('entries.*.payment_amount').exists().isNumeric().toFloat(),
|
||||
|
||||
@@ -156,13 +156,10 @@ export default class VendorCreditController extends BaseController {
|
||||
check('vendor_id').exists().isNumeric().toInt(),
|
||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
||||
|
||||
check('vendor_credit_number')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape(),
|
||||
check('reference_no').optional().trim().escape(),
|
||||
check('vendor_credit_number').optional({ nullable: true }).trim(),
|
||||
check('reference_no').optional().trim(),
|
||||
check('vendor_credit_date').exists().isISO8601().toDate(),
|
||||
check('note').optional().trim().escape(),
|
||||
check('note').optional().trim(),
|
||||
check('open').default(false).isBoolean().toBoolean(),
|
||||
|
||||
check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||
@@ -178,10 +175,7 @@ export default class VendorCreditController extends BaseController {
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toFloat(),
|
||||
check('entries.*.description')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape(),
|
||||
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||
check('entries.*.warehouse_id')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
@@ -202,13 +196,10 @@ export default class VendorCreditController extends BaseController {
|
||||
check('vendor_id').exists().isNumeric().toInt(),
|
||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
||||
|
||||
check('vendor_credit_number')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape(),
|
||||
check('reference_no').optional().trim().escape(),
|
||||
check('vendor_credit_number').optional({ nullable: true }).trim(),
|
||||
check('reference_no').optional().trim(),
|
||||
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('branch_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||
@@ -223,10 +214,7 @@ export default class VendorCreditController extends BaseController {
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toFloat(),
|
||||
check('entries.*.description')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape(),
|
||||
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||
check('entries.*.warehouse_id')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
|
||||
@@ -18,9 +18,7 @@ export default class ResourceController extends BaseController {
|
||||
|
||||
router.get(
|
||||
'/:resource_model/meta',
|
||||
[
|
||||
param('resource_model').exists().trim().escape()
|
||||
],
|
||||
[param('resource_model').exists().trim()],
|
||||
this.asyncMiddleware(this.resourceMeta.bind(this)),
|
||||
this.handleServiceErrors
|
||||
);
|
||||
@@ -48,9 +46,7 @@ export default class ResourceController extends BaseController {
|
||||
resourceModel
|
||||
);
|
||||
return res.status(200).send({
|
||||
resource_meta: this.transfromToResponse(
|
||||
resourceMeta,
|
||||
),
|
||||
resource_meta: this.transfromToResponse(resourceMeta),
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
|
||||
@@ -210,9 +210,9 @@ export default class PaymentReceivesController extends BaseController {
|
||||
|
||||
check('credit_note_date').exists().isISO8601().toDate(),
|
||||
check('reference_no').optional(),
|
||||
check('credit_note_number').optional({ nullable: true }).trim().escape(),
|
||||
check('note').optional().trim().escape(),
|
||||
check('terms_conditions').optional().trim().escape(),
|
||||
check('credit_note_number').optional({ nullable: true }).trim(),
|
||||
check('note').optional().trim(),
|
||||
check('terms_conditions').optional().trim(),
|
||||
check('open').default(false).isBoolean().toBoolean(),
|
||||
|
||||
check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||
@@ -228,10 +228,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toFloat(),
|
||||
check('entries.*.description')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape(),
|
||||
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||
check('entries.*.warehouse_id')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
|
||||
@@ -9,9 +9,9 @@ import {
|
||||
} from '@/interfaces';
|
||||
import BaseController from '@/api/controllers/BaseController';
|
||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||
import PaymentReceivesPages from '@/services/Sales/PaymentReceives/PaymentReceivesPages';
|
||||
import PaymentsReceivedPages from '@/services/Sales/PaymentReceived/PaymentsReceivedPages';
|
||||
import { PaymentReceivesApplication } from '@/services/Sales/PaymentReceived/PaymentReceivedApplication';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
import { PaymentReceivesApplication } from '@/services/Sales/PaymentReceives/PaymentReceivesApplication';
|
||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ACCEPT_TYPE } from '@/interfaces/Http';
|
||||
@@ -22,7 +22,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
private paymentReceiveApplication: PaymentReceivesApplication;
|
||||
|
||||
@Inject()
|
||||
private PaymentReceivesPages: PaymentReceivesPages;
|
||||
private PaymentsReceivedPages: PaymentsReceivedPages;
|
||||
|
||||
@Inject()
|
||||
private dynamicListService: DynamicListingService;
|
||||
@@ -150,16 +150,16 @@ export default class PaymentReceivesController extends BaseController {
|
||||
check('customer_id').exists().isNumeric().toInt(),
|
||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
||||
|
||||
check('amount').exists().isNumeric().toFloat(),
|
||||
check('payment_date').exists(),
|
||||
check('reference_no').optional(),
|
||||
check('deposit_account_id').exists().isNumeric().toInt(),
|
||||
check('payment_receive_no').optional({ nullable: true }).trim().escape(),
|
||||
check('statement').optional().trim().escape(),
|
||||
check('payment_receive_no').optional({ nullable: true }).trim(),
|
||||
check('statement').optional().trim(),
|
||||
|
||||
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.*.index').optional().isNumeric().toInt(),
|
||||
check('entries.*.invoice_id').exists().isNumeric().toInt(),
|
||||
@@ -176,7 +176,6 @@ export default class PaymentReceivesController extends BaseController {
|
||||
private get validatePaymentReceiveList(): ValidationChain[] {
|
||||
return [
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
|
||||
query('view_slug').optional({ nullable: true }).isString().trim(),
|
||||
|
||||
query('column_sort_by').optional(),
|
||||
@@ -230,7 +229,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
|
||||
try {
|
||||
const storedPaymentReceive =
|
||||
await this.paymentReceiveApplication.createPaymentReceive(
|
||||
await this.paymentReceiveApplication.createPaymentReceived(
|
||||
tenantId,
|
||||
paymentReceive,
|
||||
user
|
||||
@@ -377,7 +376,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
const { customerId } = this.matchedQueryData(req);
|
||||
|
||||
try {
|
||||
const entries = await this.PaymentReceivesPages.getNewPageEntries(
|
||||
const entries = await this.PaymentsReceivedPages.getNewPageEntries(
|
||||
tenantId,
|
||||
customerId
|
||||
);
|
||||
@@ -405,7 +404,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
|
||||
try {
|
||||
const { paymentReceive, entries } =
|
||||
await this.PaymentReceivesPages.getPaymentReceiveEditPage(
|
||||
await this.PaymentsReceivedPages.getPaymentReceiveEditPage(
|
||||
tenantId,
|
||||
paymentReceiveId,
|
||||
user
|
||||
|
||||
@@ -155,7 +155,7 @@ export default class SalesEstimatesController extends BaseController {
|
||||
check('estimate_date').exists().isISO8601().toDate(),
|
||||
check('expiration_date').exists().isISO8601().toDate(),
|
||||
check('reference').optional(),
|
||||
check('estimate_number').optional().trim().escape(),
|
||||
check('estimate_number').optional().trim(),
|
||||
check('delivered').default(false).isBoolean().toBoolean(),
|
||||
|
||||
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.*.description')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape(),
|
||||
.trim(),
|
||||
check('entries.*.discount')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
@@ -181,9 +180,9 @@ export default class SalesEstimatesController extends BaseController {
|
||||
.isNumeric()
|
||||
.toInt(),
|
||||
|
||||
check('note').optional().trim().escape(),
|
||||
check('terms_conditions').optional().trim().escape(),
|
||||
check('send_to_email').optional().trim().escape(),
|
||||
check('note').optional().trim(),
|
||||
check('terms_conditions').optional().trim(),
|
||||
check('send_to_email').optional().trim(),
|
||||
|
||||
check('attachments').isArray().optional(),
|
||||
check('attachments.*.key').exists().isString(),
|
||||
|
||||
@@ -200,12 +200,12 @@ export default class SaleInvoicesController extends BaseController {
|
||||
check('customer_id').exists().isNumeric().toInt(),
|
||||
check('invoice_date').exists().isISO8601().toDate(),
|
||||
check('due_date').exists().isISO8601().toDate(),
|
||||
check('invoice_no').optional().trim().escape(),
|
||||
check('reference_no').optional().trim().escape(),
|
||||
check('invoice_no').optional().trim(),
|
||||
check('reference_no').optional().trim(),
|
||||
check('delivered').default(false).isBoolean().toBoolean(),
|
||||
|
||||
check('invoice_message').optional().trim().escape(),
|
||||
check('terms_conditions').optional().trim().escape(),
|
||||
check('invoice_message').optional().trim(),
|
||||
check('terms_conditions').optional().trim(),
|
||||
|
||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
||||
|
||||
@@ -226,12 +226,10 @@ export default class SaleInvoicesController extends BaseController {
|
||||
.toFloat(),
|
||||
check('entries.*.description')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape(),
|
||||
.trim(),
|
||||
check('entries.*.tax_code')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape()
|
||||
.isString(),
|
||||
check('entries.*.tax_rate_id')
|
||||
.optional({ nullable: true })
|
||||
|
||||
@@ -130,8 +130,8 @@ export default class SalesReceiptsController extends BaseController {
|
||||
|
||||
check('deposit_account_id').exists().isNumeric().toInt(),
|
||||
check('receipt_date').exists().isISO8601(),
|
||||
check('receipt_number').optional().trim().escape(),
|
||||
check('reference_no').optional().trim().escape(),
|
||||
check('receipt_number').optional().trim(),
|
||||
check('reference_no').optional().trim(),
|
||||
check('closed').default(false).isBoolean().toBoolean(),
|
||||
|
||||
check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||
@@ -150,14 +150,13 @@ export default class SalesReceiptsController extends BaseController {
|
||||
.toInt(),
|
||||
check('entries.*.description')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape(),
|
||||
.trim(),
|
||||
check('entries.*.warehouse_id')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toInt(),
|
||||
check('receipt_message').optional().trim().escape(),
|
||||
check('statement').optional().trim().escape(),
|
||||
check('receipt_message').optional().trim(),
|
||||
check('statement').optional().trim(),
|
||||
check('attachments').isArray().optional(),
|
||||
check('attachments.*.key').exists().isString(),
|
||||
];
|
||||
|
||||
@@ -52,10 +52,7 @@ export default class SettingsController extends BaseController {
|
||||
* Retrieve the application options from the storage.
|
||||
*/
|
||||
private get getSettingsSchema() {
|
||||
return [
|
||||
query('key').optional().trim().escape(),
|
||||
query('group').optional().trim().escape(),
|
||||
];
|
||||
return [query('key').optional().trim(), query('group').optional().trim()];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,7 @@ import SubscriptionService from '@/services/Subscription/SubscriptionService';
|
||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||
import BaseController from '../BaseController';
|
||||
import { LemonSqueezyService } from '@/services/Subscription/LemonSqueezyService';
|
||||
import { SubscriptionApplication } from '@/services/Subscription/SubscriptionApplication';
|
||||
|
||||
@Service()
|
||||
export class SubscriptionController extends BaseController {
|
||||
@@ -17,6 +18,9 @@ export class SubscriptionController extends BaseController {
|
||||
@Inject()
|
||||
private lemonSqueezyService: LemonSqueezyService;
|
||||
|
||||
@Inject()
|
||||
private subscriptionApp: SubscriptionApplication;
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
@@ -33,6 +37,14 @@ export class SubscriptionController extends BaseController {
|
||||
this.validationResult,
|
||||
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)));
|
||||
|
||||
return router;
|
||||
@@ -85,4 +97,84 @@ export class SubscriptionController extends BaseController {
|
||||
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);
|
||||
|
||||
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.
|
||||
*/
|
||||
get viewsListSchemaValidation() {
|
||||
return [param('resource_model').exists().trim().escape()];
|
||||
return [param('resource_model').exists().trim()];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,7 +35,7 @@ export class Webhooks extends BaseController {
|
||||
*/
|
||||
public async lemonWebhooks(req: Request, res: Response, next: NextFunction) {
|
||||
const data = req.body;
|
||||
const signature = req.headers['x-signature'] ?? '';
|
||||
const signature = req.headers['x-signature'] as string ?? '';
|
||||
const rawBody = req.rawBody;
|
||||
|
||||
try {
|
||||
|
||||
@@ -63,6 +63,7 @@ import { BankingController } from './controllers/Banking/BankingController';
|
||||
import { Webhooks } from './controllers/Webhooks/Webhooks';
|
||||
import { ExportController } from './controllers/Export/ExportController';
|
||||
import { AttachmentsController } from './controllers/Attachments/AttachmentsController';
|
||||
import { OneClickDemoController } from './controllers/OneClickDemo/OneClickDemoController';
|
||||
|
||||
export default () => {
|
||||
const app = Router();
|
||||
@@ -80,6 +81,7 @@ export default () => {
|
||||
app.use('/jobs', Container.get(Jobs).router());
|
||||
app.use('/account', Container.get(Account).router());
|
||||
app.use('/webhooks', Container.get(Webhooks).router());
|
||||
app.use('/demo', Container.get(OneClickDemoController).router())
|
||||
|
||||
// - Dashboard routes.
|
||||
// ---------------------------
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Container } from 'typedi';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import SettingsStore from '@/services/Settings/SettingsStore';
|
||||
|
||||
|
||||
export default async (req: Request, res: Response, next: NextFunction) => {
|
||||
const { tenantId } = req.user;
|
||||
|
||||
const Logger = Container.get('logger');
|
||||
const settings = await initializeTenantSettings(tenantId);
|
||||
req.settings = settings;
|
||||
|
||||
res.on('finish', async () => {
|
||||
await settings.save();
|
||||
});
|
||||
next();
|
||||
}
|
||||
|
||||
|
||||
export const initializeTenantSettings = async (tenantId: number) => {
|
||||
const tenantContainer = Container.of(`tenant-${tenantId}`);
|
||||
|
||||
if (tenantContainer && !tenantContainer.has('settings')) {
|
||||
@@ -18,10 +28,5 @@ export default async (req: Request, res: Response, next: NextFunction) => {
|
||||
|
||||
await settings.load();
|
||||
|
||||
req.settings = settings;
|
||||
|
||||
res.on('finish', async () => {
|
||||
await settings.save();
|
||||
});
|
||||
next();
|
||||
return settings;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { Request } from 'express';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import TenantsManagerService from '@/services/Tenancy/TenantsManager';
|
||||
import rtlDetect from 'rtl-detect';
|
||||
import { Tenant } from '@/system/models';
|
||||
|
||||
export default (req: Request, tenant: ITenant) => {
|
||||
const { id: tenantId, organizationId } = tenant;
|
||||
@@ -16,7 +17,7 @@ export default (req: Request, tenant: ITenant) => {
|
||||
|
||||
const tenantContainer = tenantServices.tenantContainer(tenantId);
|
||||
|
||||
tenantContainer.set('i18n', injectI18nUtils(req));
|
||||
tenantContainer.set('i18n', injectI18nUtils());
|
||||
|
||||
const knexInstance = tenantServices.knex(tenantId);
|
||||
const models = tenantServices.models(tenantId);
|
||||
@@ -33,14 +34,35 @@ export default (req: Request, tenant: ITenant) => {
|
||||
};
|
||||
|
||||
export const injectI18nUtils = (req) => {
|
||||
const locale = req.getLocale();
|
||||
const globalI18n = Container.get('i18n');
|
||||
const locale = globalI18n.getLocale();
|
||||
const direction = rtlDetect.getLangDir(locale);
|
||||
|
||||
return {
|
||||
locale,
|
||||
__: req.__,
|
||||
__: globalI18n.__,
|
||||
direction,
|
||||
isRtl: direction === 'rtl',
|
||||
isLtr: direction === 'ltr',
|
||||
};
|
||||
};
|
||||
|
||||
export const initalizeTenantServices = async (tenantId: number) => {
|
||||
const tenant = await Tenant.query()
|
||||
.findById(tenantId)
|
||||
.withGraphFetched('metadata');
|
||||
|
||||
const tenantServices = Container.get(TenancyService);
|
||||
const tenantsManager = Container.get(TenantsManagerService);
|
||||
|
||||
// Initialize the knex instance.
|
||||
tenantsManager.setupKnexInstance(tenant);
|
||||
|
||||
const tenantContainer = tenantServices.tenantContainer(tenantId);
|
||||
tenantContainer.set('i18n', injectI18nUtils());
|
||||
|
||||
tenantServices.knex(tenantId);
|
||||
tenantServices.models(tenantId);
|
||||
tenantServices.repositories(tenantId);
|
||||
tenantServices.cache(tenantId);
|
||||
};
|
||||
|
||||
@@ -32,7 +32,7 @@ module.exports = {
|
||||
*/
|
||||
tenant: {
|
||||
db_client: process.env.TENANT_DB_CLIENT || process.env.DB_CLIENT || 'mysql',
|
||||
db_name_prefix: process.env.TENANT_DB_NAME_PERFIX,
|
||||
db_name_prefix: process.env.TENANT_DB_NAME_PERFIX || 'bigcapital_tenant_',
|
||||
db_host: process.env.TENANT_DB_HOST || process.env.DB_HOST,
|
||||
db_user: process.env.TENANT_DB_USER || process.env.DB_USER,
|
||||
db_password: process.env.TENANT_DB_PASSWORD || process.env.DB_PASSWORD,
|
||||
@@ -236,5 +236,21 @@ module.exports = {
|
||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
||||
endpoint: process.env.S3_ENDPOINT,
|
||||
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,
|
||||
},
|
||||
|
||||
/**
|
||||
* One-click demo accounts.
|
||||
*/
|
||||
oneClickDemoAccounts: {
|
||||
enable: parseBoolean(process.env.ONE_CLICK_DEMO_ACCOUNTS, false),
|
||||
demoUrl: process.env.ONE_CLICK_DEMO_ACCOUNTS_URL || '',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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),
|
||||
currencyCode: this.tenant.metadata.baseCurrency,
|
||||
seededAt: new Date(),
|
||||
})
|
||||
);
|
||||
}));
|
||||
return knex('accounts').then(async () => {
|
||||
// Inserts seed entries.
|
||||
return knex('accounts').insert(data);
|
||||
|
||||
@@ -15,6 +15,7 @@ export default class SeedSettings extends TenantSeeder {
|
||||
|
||||
// Manual journals settings.
|
||||
{ group: 'manual_journals', key: 'next_number', value: '00001' },
|
||||
{ group: 'manual_journals', key: 'number_prefix', value: 'J-' },
|
||||
{ group: 'manual_journals', key: 'auto_increment', value: true },
|
||||
|
||||
// Sale invoices settings.
|
||||
|
||||
@@ -9,6 +9,28 @@ export const TaxPayableAccount = {
|
||||
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 [
|
||||
{
|
||||
name: 'Bank Account',
|
||||
@@ -323,4 +345,6 @@ export default [
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
UnearnedRevenueAccount,
|
||||
PrepardExpenses,
|
||||
];
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface IAccountDTO {
|
||||
export interface IAccountCreateDTO extends IAccountDTO {
|
||||
currencyCode?: string;
|
||||
plaidAccountId?: string;
|
||||
plaidItemId?: string;
|
||||
}
|
||||
|
||||
export interface IAccountEditDTO extends IAccountDTO {}
|
||||
@@ -37,6 +38,8 @@ export interface IAccount {
|
||||
accountNormal: string;
|
||||
accountParentType: string;
|
||||
bankBalance: string;
|
||||
plaidItemId: number | null
|
||||
lastFeedsUpdatedAt: Date;
|
||||
}
|
||||
|
||||
export enum AccountNormal {
|
||||
|
||||
@@ -7,7 +7,6 @@ export interface IRegisterDTO {
|
||||
lastName: string;
|
||||
email: string;
|
||||
password: string;
|
||||
organizationName: string;
|
||||
}
|
||||
|
||||
export interface ILoginDTO {
|
||||
@@ -77,6 +76,10 @@ export interface IAuthSendedResetPassword {
|
||||
|
||||
export interface IAuthGetMetaPOJO {
|
||||
signupDisabled: boolean;
|
||||
oneClickDemo: {
|
||||
enable: boolean;
|
||||
demoUrl: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IAuthSignUpVerifingEventPayload {
|
||||
|
||||
@@ -236,6 +236,7 @@ export interface ICashflowTransactionSchema {
|
||||
export interface ICashflowTransactionInput extends ICashflowTransactionSchema {}
|
||||
|
||||
export interface ICategorizeCashflowTransactioDTO {
|
||||
date: Date;
|
||||
creditAccountId: number;
|
||||
referenceNo: string;
|
||||
transactionNumber: string;
|
||||
@@ -267,6 +268,8 @@ export interface CreateUncategorizedTransactionDTO {
|
||||
description?: string;
|
||||
referenceNo?: string | null;
|
||||
plaidTransactionId?: string | null;
|
||||
pending?: boolean;
|
||||
pendingPlaidTransactionId?: string | null;
|
||||
batch?: string;
|
||||
}
|
||||
|
||||
@@ -282,3 +285,17 @@ export interface IUncategorizedTransactionCreatedEventPayload {
|
||||
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,20 +130,23 @@ export interface ICommandCashflowDeletedPayload {
|
||||
|
||||
export interface ICashflowTransactionCategorizedPayload {
|
||||
tenantId: number;
|
||||
uncategorizedTransaction: any;
|
||||
uncategorizedTransactions: Array<IUncategorizedCashflowTransaction>;
|
||||
cashflowTransaction: ICashflowTransaction;
|
||||
oldUncategorizedTransactions: Array<IUncategorizedCashflowTransaction>;
|
||||
categorizeDTO: any;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
export interface ICashflowTransactionUncategorizingPayload {
|
||||
tenantId: number;
|
||||
uncategorizedTransaction: IUncategorizedCashflowTransaction;
|
||||
uncategorizedTransactionId: number;
|
||||
oldUncategorizedTransactions: Array<IUncategorizedCashflowTransaction>;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
export interface ICashflowTransactionUncategorizedPayload {
|
||||
tenantId: number;
|
||||
uncategorizedTransaction: IUncategorizedCashflowTransaction;
|
||||
oldUncategorizedTransaction: IUncategorizedCashflowTransaction;
|
||||
uncategorizedTransactionId: number;
|
||||
uncategorizedTransactions: Array<IUncategorizedCashflowTransaction>;
|
||||
oldUncategorizedTransactions: Array<IUncategorizedCashflowTransaction>;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
@@ -164,11 +167,18 @@ export interface CategorizeTransactionAsExpenseDTO {
|
||||
export interface IGetUncategorizedTransactionsQuery {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
minDate?: Date;
|
||||
maxDate?: Date;
|
||||
minAmount?: number;
|
||||
maxAmount?: number;
|
||||
}
|
||||
|
||||
|
||||
export interface IGetRecognizedTransactionsQuery {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
accountId?: number;
|
||||
}
|
||||
minDate?: Date;
|
||||
maxDate?: Date;
|
||||
minAmount?: number;
|
||||
maxAmount?: number;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export interface IGeneralLedgerSheetAccountTransaction {
|
||||
currencyCode: string;
|
||||
note?: string;
|
||||
|
||||
transactionType?: string;
|
||||
transactionTypeFormatted: string;
|
||||
transactionNumber: string;
|
||||
|
||||
referenceId?: number;
|
||||
|
||||
@@ -3,6 +3,6 @@ import { ImportFilePreviewPOJO } from "@/services/Import/interfaces";
|
||||
|
||||
export interface IImportFileCommitedEventPayload {
|
||||
tenantId: number;
|
||||
importId: number;
|
||||
importId: string;
|
||||
meta: ImportFilePreviewPOJO;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export interface ILedgerEntry {
|
||||
date: Date | string;
|
||||
|
||||
transactionType: string;
|
||||
transactionSubType: string;
|
||||
transactionSubType?: string;
|
||||
|
||||
transactionId: number;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ILedgerEntry } from './Ledger';
|
||||
import { ISaleInvoice } from './SaleInvoice';
|
||||
import { AttachmentLinkDTO } from './Attachments';
|
||||
|
||||
export interface IPaymentReceive {
|
||||
export interface IPaymentReceived {
|
||||
id?: number;
|
||||
customerId: number;
|
||||
paymentDate: Date;
|
||||
@@ -19,14 +19,14 @@ export interface IPaymentReceive {
|
||||
depositAccountId: number;
|
||||
paymentReceiveNo: string;
|
||||
statement: string;
|
||||
entries: IPaymentReceiveEntry[];
|
||||
entries: IPaymentReceivedEntry[];
|
||||
userId: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
localAmount?: number;
|
||||
branchId?: number;
|
||||
}
|
||||
export interface IPaymentReceiveCreateDTO {
|
||||
export interface IPaymentReceivedCreateDTO {
|
||||
customerId: number;
|
||||
paymentDate: Date;
|
||||
amount: number;
|
||||
@@ -35,13 +35,13 @@ export interface IPaymentReceiveCreateDTO {
|
||||
depositAccountId: number;
|
||||
paymentReceiveNo?: string;
|
||||
statement: string;
|
||||
entries: IPaymentReceiveEntryDTO[];
|
||||
entries: IPaymentReceivedEntryDTO[];
|
||||
|
||||
branchId?: number;
|
||||
attachments?: AttachmentLinkDTO[];
|
||||
}
|
||||
|
||||
export interface IPaymentReceiveEditDTO {
|
||||
export interface IPaymentReceivedEditDTO {
|
||||
customerId: number;
|
||||
paymentDate: Date;
|
||||
amount: number;
|
||||
@@ -50,12 +50,12 @@ export interface IPaymentReceiveEditDTO {
|
||||
depositAccountId: number;
|
||||
paymentReceiveNo?: string;
|
||||
statement: string;
|
||||
entries: IPaymentReceiveEntryDTO[];
|
||||
entries: IPaymentReceivedEntryDTO[];
|
||||
branchId?: number;
|
||||
attachments?: AttachmentLinkDTO[];
|
||||
}
|
||||
|
||||
export interface IPaymentReceiveEntry {
|
||||
export interface IPaymentReceivedEntry {
|
||||
id?: number;
|
||||
paymentReceiveId: number;
|
||||
invoiceId: number;
|
||||
@@ -64,15 +64,15 @@ export interface IPaymentReceiveEntry {
|
||||
invoice?: ISaleInvoice;
|
||||
}
|
||||
|
||||
export interface IPaymentReceiveEntryDTO {
|
||||
export interface IPaymentReceivedEntryDTO {
|
||||
id?: number;
|
||||
index: number;
|
||||
paymentReceiveId: number;
|
||||
paymentReceiveId?: number;
|
||||
invoiceId: number;
|
||||
paymentAmount: number;
|
||||
}
|
||||
|
||||
export interface IPaymentReceivesFilter extends IDynamicListFilterDTO {
|
||||
export interface IPaymentsReceivedFilter extends IDynamicListFilterDTO {
|
||||
stringifiedFilterRoles?: string;
|
||||
}
|
||||
|
||||
@@ -88,65 +88,65 @@ export interface IPaymentReceivePageEntry {
|
||||
date: Date | string;
|
||||
}
|
||||
|
||||
export interface IPaymentReceiveEditPage {
|
||||
paymentReceive: IPaymentReceive;
|
||||
export interface IPaymentReceivedEditPage {
|
||||
paymentReceive: IPaymentReceived;
|
||||
entries: IPaymentReceivePageEntry[];
|
||||
}
|
||||
|
||||
export interface IPaymentsReceiveService {
|
||||
export interface IPaymentsReceivedService {
|
||||
validateCustomerHasNoPayments(
|
||||
tenantId: number,
|
||||
customerId: number
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IPaymentReceiveSmsDetails {
|
||||
export interface IPaymentReceivedSmsDetails {
|
||||
customerName: string;
|
||||
customerPhoneNumber: string;
|
||||
smsMessage: string;
|
||||
}
|
||||
|
||||
export interface IPaymentReceiveCreatingPayload {
|
||||
export interface IPaymentReceivedCreatingPayload {
|
||||
tenantId: number;
|
||||
paymentReceiveDTO: IPaymentReceiveCreateDTO;
|
||||
paymentReceiveDTO: IPaymentReceivedCreateDTO;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IPaymentReceiveCreatedPayload {
|
||||
export interface IPaymentReceivedCreatedPayload {
|
||||
tenantId: number;
|
||||
paymentReceive: IPaymentReceive;
|
||||
paymentReceive: IPaymentReceived;
|
||||
paymentReceiveId: number;
|
||||
authorizedUser: ISystemUser;
|
||||
paymentReceiveDTO: IPaymentReceiveCreateDTO;
|
||||
paymentReceiveDTO: IPaymentReceivedCreateDTO;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IPaymentReceiveEditedPayload {
|
||||
export interface IPaymentReceivedEditedPayload {
|
||||
tenantId: number;
|
||||
paymentReceiveId: number;
|
||||
paymentReceive: IPaymentReceive;
|
||||
oldPaymentReceive: IPaymentReceive;
|
||||
paymentReceiveDTO: IPaymentReceiveEditDTO;
|
||||
paymentReceive: IPaymentReceived;
|
||||
oldPaymentReceive: IPaymentReceived;
|
||||
paymentReceiveDTO: IPaymentReceivedEditDTO;
|
||||
authorizedUser: ISystemUser;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IPaymentReceiveEditingPayload {
|
||||
export interface IPaymentReceivedEditingPayload {
|
||||
tenantId: number;
|
||||
oldPaymentReceive: IPaymentReceive;
|
||||
paymentReceiveDTO: IPaymentReceiveEditDTO;
|
||||
oldPaymentReceive: IPaymentReceived;
|
||||
paymentReceiveDTO: IPaymentReceivedEditDTO;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IPaymentReceiveDeletingPayload {
|
||||
export interface IPaymentReceivedDeletingPayload {
|
||||
tenantId: number;
|
||||
oldPaymentReceive: IPaymentReceive;
|
||||
oldPaymentReceive: IPaymentReceived;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
export interface IPaymentReceiveDeletedPayload {
|
||||
export interface IPaymentReceivedDeletedPayload {
|
||||
tenantId: number;
|
||||
paymentReceiveId: number;
|
||||
oldPaymentReceive: IPaymentReceive;
|
||||
oldPaymentReceive: IPaymentReceived;
|
||||
authorizedUser: ISystemUser;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
@@ -33,3 +33,7 @@ export interface IOrganizationBuildEventPayload {
|
||||
buildDTO: IOrganizationBuildDTO;
|
||||
systemUser: ISystemUser;
|
||||
}
|
||||
|
||||
export interface IOrganizationBuiltEventPayload {
|
||||
tenantId: number;
|
||||
}
|
||||
8
packages/server/src/interfaces/Subscription.ts
Normal file
8
packages/server/src/interfaces/Subscription.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface SubscriptionPayload {
|
||||
lemonSqueezyId?: string;
|
||||
}
|
||||
|
||||
export enum SubscriptionPaymentStatus {
|
||||
Succeed = 'succeed',
|
||||
Failed = 'failed',
|
||||
}
|
||||
@@ -51,5 +51,4 @@ export interface ISystemService {
|
||||
cache();
|
||||
repositories();
|
||||
knex();
|
||||
dbManager();
|
||||
}
|
||||
@@ -75,6 +75,7 @@ export * from './Times';
|
||||
export * from './ProjectProfitabilitySummary';
|
||||
export * from './TaxRate';
|
||||
export * from './Plaid';
|
||||
export * from './Subscription';
|
||||
|
||||
export interface I18nService {
|
||||
__: (input: string) => string;
|
||||
|
||||
@@ -21,7 +21,7 @@ export default class ComputeItemCostJob {
|
||||
|
||||
agenda.define(
|
||||
'compute-item-cost',
|
||||
{ priority: 'high', concurrency: 1 },
|
||||
{ priority: 'high', concurrency: 20 },
|
||||
this.handler.bind(this)
|
||||
);
|
||||
this.agenda.on('start:compute-item-cost', this.onJobStart.bind(this));
|
||||
|
||||
@@ -8,7 +8,7 @@ export default class OrganizationSetupJob {
|
||||
constructor(agenda) {
|
||||
agenda.define(
|
||||
'organization-setup',
|
||||
{ priority: 'high', concurrency: 1 },
|
||||
{ priority: 'high', concurrency: 20 },
|
||||
this.handler
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export default class WriteInvoicesJournalEntries {
|
||||
|
||||
agenda.define(
|
||||
eventName,
|
||||
{ priority: 'normal', concurrency: 1 },
|
||||
{ priority: 'normal', concurrency: 20 },
|
||||
this.handler.bind(this)
|
||||
);
|
||||
agenda.on(`complete:${eventName}`, this.onJobCompleted.bind(this));
|
||||
|
||||
@@ -1,69 +1,12 @@
|
||||
import { forEach } from 'lodash';
|
||||
import { Configuration, PlaidApi, PlaidEnvironments } from 'plaid';
|
||||
import { createPlaidApiEvent } from './PlaidApiEventsDBSync';
|
||||
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.
|
||||
export class PlaidClientWrapper {
|
||||
constructor() {
|
||||
private static instance: PlaidClientWrapper;
|
||||
private client: PlaidApi;
|
||||
|
||||
private constructor() {
|
||||
// Initialize the Plaid client.
|
||||
const configuration = new Configuration({
|
||||
basePath: PlaidEnvironments[config.plaid.env],
|
||||
@@ -75,26 +18,13 @@ export class PlaidClientWrapper {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
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.
|
||||
createWrappedClientMethod(clientMethod, log) {
|
||||
return async (...args) => {
|
||||
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;
|
||||
}
|
||||
};
|
||||
public static getClient(): PlaidApi {
|
||||
if (!PlaidClientWrapper.instance) {
|
||||
PlaidClientWrapper.instance = new PlaidClientWrapper();
|
||||
}
|
||||
return PlaidClientWrapper.instance.client;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,4 +8,5 @@ export const s3 = new S3Client({
|
||||
secretAccessKey: config.s3.secretAccessKey,
|
||||
},
|
||||
endpoint: config.s3.endpoint,
|
||||
forcePathStyle: config.s3.forcePathStyle,
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import { includes, isFunction, isObject, isUndefined, omit } from 'lodash';
|
||||
import { formatNumber, sortObjectKeysAlphabetically } from 'utils';
|
||||
import { EXPORT_DTE_FORMAT } from '@/services/Export/constants';
|
||||
|
||||
export class Transformer {
|
||||
public context: any;
|
||||
@@ -155,19 +156,35 @@ export class Transformer {
|
||||
this.dateFormat = format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date.
|
||||
* @param {} date
|
||||
* @param {string} format -
|
||||
* @returns {}
|
||||
*/
|
||||
protected formatDate(date, format?: string) {
|
||||
// Use the export date format if the async operation is in exporting,
|
||||
// otherwise use the given or default format.
|
||||
const _format = this.context.exportAls.isExport
|
||||
? EXPORT_DTE_FORMAT
|
||||
: format || this.dateFormat;
|
||||
|
||||
return date ? moment(date).format(_format) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param date
|
||||
* @returns
|
||||
* @returns {}
|
||||
*/
|
||||
protected formatDate(date) {
|
||||
return date ? moment(date).format(this.dateFormat) : '';
|
||||
protected formatDateFromNow(date) {
|
||||
return date ? moment(date).fromNow(true) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param number
|
||||
* @returns
|
||||
* @returns {}
|
||||
*/
|
||||
protected formatNumber(number, props?) {
|
||||
return formatNumber(number, { money: false, ...props });
|
||||
@@ -177,7 +194,7 @@ export class Transformer {
|
||||
*
|
||||
* @param money
|
||||
* @param options
|
||||
* @returns
|
||||
* @returns {}
|
||||
*/
|
||||
protected formatMoney(money, options?) {
|
||||
return formatNumber(money, {
|
||||
|
||||
@@ -3,12 +3,17 @@ import { isNull } from 'lodash';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { TenantMetadata } from '@/system/models';
|
||||
import { Transformer } from './Transformer';
|
||||
import { ImportAls } from '@/services/Import/ImportALS';
|
||||
import { ExportAls } from '@/services/Export/ExportAls';
|
||||
|
||||
@Service()
|
||||
export class TransformerInjectable {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private exportAls: ExportAls;
|
||||
|
||||
/**
|
||||
* Retrieves the application context of all tenant transformers.
|
||||
* @param {number} tenantId
|
||||
@@ -17,10 +22,12 @@ export class TransformerInjectable {
|
||||
async getApplicationContext(tenantId: number) {
|
||||
const i18n = this.tenancy.i18n(tenantId);
|
||||
const organization = await TenantMetadata.query().findOne({ tenantId });
|
||||
const exportAls = this.exportAls;
|
||||
|
||||
return {
|
||||
organization,
|
||||
i18n,
|
||||
exportAls,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import knexManager from 'knex-db-manager';
|
||||
import { systemKnexConfig, systemDbManager } from 'config/knexConfig';
|
||||
|
||||
export default () => knexManager.databaseManagerFactory({
|
||||
knex: systemKnexConfig,
|
||||
dbManager: systemDbManager,
|
||||
});
|
||||
@@ -3,7 +3,6 @@ import LoggerInstance from '@/loaders/logger';
|
||||
import agendaFactory from '@/loaders/agenda';
|
||||
import SmsClientLoader from '@/loaders/smsClient';
|
||||
import mailInstance from '@/loaders/mail';
|
||||
import dbManagerFactory from '@/loaders/dbManager';
|
||||
import i18n from '@/loaders/i18n';
|
||||
import repositoriesLoader from '@/loaders/systemRepositories';
|
||||
import Cache from '@/services/Cache';
|
||||
@@ -16,7 +15,6 @@ export default ({ mongoConnection, knex }) => {
|
||||
try {
|
||||
const agendaInstance = agendaFactory({ mongoConnection });
|
||||
const smsClientInstance = SmsClientLoader(config.easySMSGateway.api_key);
|
||||
const dbManager = dbManagerFactory(knex);
|
||||
const cacheInstance = new Cache();
|
||||
|
||||
Container.set('logger', LoggerInstance);
|
||||
@@ -24,7 +22,6 @@ export default ({ mongoConnection, knex }) => {
|
||||
Container.set('SMSClient', smsClientInstance);
|
||||
Container.set('mail', mailInstance);
|
||||
|
||||
Container.set('dbManager', dbManager);
|
||||
LoggerInstance.info(
|
||||
'[DI] Database manager has been injected into container.'
|
||||
);
|
||||
|
||||
@@ -113,6 +113,11 @@ import { UnlinkBankRuleOnDeleteBankRule } from '@/services/Banking/Rules/events/
|
||||
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';
|
||||
import { DeleteUncategorizedTransactionsOnAccountDeleting } from '@/services/Banking/BankAccounts/events/DeleteUncategorizedTransactionsOnAccountDeleting';
|
||||
import { SeedInitialDemoAccountDataOnOrgBuild } from '@/services/OneClickDemo/events/SeedInitialDemoAccountData';
|
||||
import { TriggerInvalidateCacheOnSubscriptionChange } from '@/services/Subscription/events/TriggerInvalidateCacheOnSubscriptionChange';
|
||||
|
||||
export default () => {
|
||||
return new EventPublisher();
|
||||
@@ -243,8 +248,10 @@ export const susbcribers = () => {
|
||||
DeleteCashflowTransactionOnUncategorize,
|
||||
PreventDeleteTransactionOnDelete,
|
||||
|
||||
// Subscription
|
||||
SubscribeFreeOnSignupCommunity,
|
||||
SendVerfiyMailOnSignUp,
|
||||
TriggerInvalidateCacheOnSubscriptionChange,
|
||||
|
||||
// Attachments
|
||||
AttachmentsOnSaleInvoiceCreated,
|
||||
@@ -274,5 +281,13 @@ export const susbcribers = () => {
|
||||
|
||||
// Plaid
|
||||
RecognizeSyncedBankTranasctions,
|
||||
DisconnectPlaidItemOnAccountDeleted,
|
||||
DeleteUncategorizedTransactionsOnAccountDeleting,
|
||||
|
||||
// Loops
|
||||
LoopsEventsSubscriber,
|
||||
|
||||
// Demo Account
|
||||
SeedInitialDemoAccountDataOnOrgBuild,
|
||||
];
|
||||
};
|
||||
|
||||
@@ -34,4 +34,4 @@
|
||||
// import 'services/Sales/SaleInvoiceWriteoffSubscriber';
|
||||
// import 'subscribers/SaleInvoices/SendSmsNotificationToCustomer';
|
||||
// import 'subscribers/SaleReceipt/SendNotificationToCustomer';
|
||||
// import 'services/Sales/PaymentReceives/PaymentReceiveSmsSubscriber';
|
||||
// import 'services/Sales/PaymentReceived/PaymentReceiveSmsSubscriber';
|
||||
@@ -2,6 +2,7 @@ import { I18n } from 'i18n';
|
||||
|
||||
export default () => new I18n({
|
||||
locales: ['en', 'ar'],
|
||||
defaultLocale: 'en',
|
||||
register: global,
|
||||
directory: global.__locales_dir,
|
||||
updateFiles: false,
|
||||
|
||||
@@ -9,11 +9,13 @@ import { SendSaleInvoiceMailJob } from '@/services/Sales/Invoices/SendSaleInvoic
|
||||
import { SendSaleInvoiceReminderMailJob } from '@/services/Sales/Invoices/SendSaleInvoiceMailReminderJob';
|
||||
import { SendSaleEstimateMailJob } from '@/services/Sales/Estimates/SendSaleEstimateMailJob';
|
||||
import { SaleReceiptMailNotificationJob } from '@/services/Sales/Receipts/SaleReceiptMailNotificationJob';
|
||||
import { PaymentReceiveMailNotificationJob } from '@/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob';
|
||||
import { PaymentReceivedMailNotificationJob } from '@/services/Sales/PaymentReceived/PaymentReceivedMailNotificationJob';
|
||||
import { PlaidFetchTransactionsJob } from '@/services/Banking/Plaid/PlaidFetchTransactionsJob';
|
||||
import { ImportDeleteExpiredFilesJobs } from '@/services/Import/jobs/ImportDeleteExpiredFilesJob';
|
||||
import { SendVerifyMailJob } from '@/services/Authentication/jobs/SendVerifyMailJob';
|
||||
import { RegonizeTransactionsJob } from '@/services/Banking/RegonizeTranasctions/RecognizeTransactionsJob';
|
||||
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 }) => {
|
||||
new ResetPasswordMailJob(agenda);
|
||||
@@ -26,11 +28,13 @@ export default ({ agenda }: { agenda: Agenda }) => {
|
||||
new SendSaleInvoiceReminderMailJob(agenda);
|
||||
new SendSaleEstimateMailJob(agenda);
|
||||
new SaleReceiptMailNotificationJob(agenda);
|
||||
new PaymentReceiveMailNotificationJob(agenda);
|
||||
new PaymentReceivedMailNotificationJob(agenda);
|
||||
new PlaidFetchTransactionsJob(agenda);
|
||||
new ImportDeleteExpiredFilesJobs(agenda);
|
||||
new SendVerifyMailJob(agenda);
|
||||
new RegonizeTransactionsJob(agenda);
|
||||
new ReregonizeTransactionsJob(agenda);
|
||||
new RevertRegonizeTransactionsJob(agenda);
|
||||
|
||||
agenda.start().then(() => {
|
||||
agenda.every('1 hours', 'delete-expired-imported-files', {});
|
||||
|
||||
@@ -197,6 +197,7 @@ export default class Account extends mixin(TenantModel, [
|
||||
const ExpenseEntry = require('models/ExpenseCategory');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const UncategorizedTransaction = require('models/UncategorizedCashflowTransaction');
|
||||
const PlaidItem = require('models/PlaidItem');
|
||||
|
||||
return {
|
||||
/**
|
||||
@@ -321,6 +322,18 @@ export default class Account extends mixin(TenantModel, [
|
||||
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',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -525,9 +525,9 @@ export default class Bill extends mixin(TenantModel, [
|
||||
return notFoundBillsIds;
|
||||
}
|
||||
|
||||
static changePaymentAmount(billId, amount) {
|
||||
static changePaymentAmount(billId, amount, trx) {
|
||||
const changeMethod = amount > 0 ? 'increment' : 'decrement';
|
||||
return this.query()
|
||||
return this.query(trx)
|
||||
.where('id', billId)
|
||||
[changeMethod]('payment_amount', Math.abs(amount));
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import BillPaymentSettings from './BillPayment.Settings';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceives/constants';
|
||||
import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceived/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class BillPayment extends mixin(TenantModel, [
|
||||
|
||||
@@ -95,6 +95,11 @@ export default {
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
customerType: {
|
||||
name: 'Customer Type',
|
||||
type: 'text',
|
||||
accessor: 'formattedCustomerType',
|
||||
},
|
||||
firstName: {
|
||||
name: 'vendor.field.first_name',
|
||||
type: 'text',
|
||||
@@ -135,116 +140,116 @@ export default {
|
||||
openingBalance: {
|
||||
name: 'vendor.field.opening_balance',
|
||||
type: 'number',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
openingBalanceAt: {
|
||||
name: 'vendor.field.opening_balance_at',
|
||||
type: 'date',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'vendor.field.currency',
|
||||
type: 'text',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
status: {
|
||||
name: 'vendor.field.status',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
note: {
|
||||
name: 'vendor.field.note',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
// Billing Address
|
||||
billingAddress1: {
|
||||
name: 'Billing Address 1',
|
||||
column: 'billing_address1',
|
||||
type: 'text',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
billingAddress2: {
|
||||
name: 'Billing Address 2',
|
||||
column: 'billing_address2',
|
||||
type: 'text',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
billingAddressCity: {
|
||||
name: 'Billing Address City',
|
||||
column: 'billing_address_city',
|
||||
type: 'text',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
billingAddressCountry: {
|
||||
name: 'Billing Address Country',
|
||||
column: 'billing_address_country',
|
||||
type: 'text',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
billingAddressPostcode: {
|
||||
name: 'Billing Address Postcode',
|
||||
column: 'billing_address_postcode',
|
||||
type: 'text',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
billingAddressState: {
|
||||
name: 'Billing Address State',
|
||||
column: 'billing_address_state',
|
||||
type: 'text',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
billingAddressPhone: {
|
||||
name: 'Billing Address Phone',
|
||||
column: 'billing_address_phone',
|
||||
type: 'text',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
// Shipping Address
|
||||
shippingAddress1: {
|
||||
name: 'Shipping Address 1',
|
||||
column: 'shipping_address1',
|
||||
type: 'text',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
shippingAddress2: {
|
||||
name: 'Shipping Address 2',
|
||||
column: 'shipping_address2',
|
||||
type: 'text',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
shippingAddressCity: {
|
||||
name: 'Shipping Address City',
|
||||
column: 'shipping_address_city',
|
||||
type: 'text',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
shippingAddressCountry: {
|
||||
name: 'Shipping Address Country',
|
||||
column: 'shipping_address_country',
|
||||
type: 'text',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
shippingAddressPostcode: {
|
||||
name: 'Shipping Address Postcode',
|
||||
column: 'shipping_address_postcode',
|
||||
type: 'text',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
shippingAddressPhone: {
|
||||
name: 'Shipping Address Phone',
|
||||
column: 'shipping_address_phone',
|
||||
type: 'text',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
shippingAddressState: {
|
||||
name: 'Shipping Address State',
|
||||
column: 'shipping_address_state',
|
||||
type: 'text',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
createdAt: {
|
||||
name: 'vendor.field.created_at',
|
||||
type: 'date',
|
||||
printable: false
|
||||
printable: false,
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
|
||||
@@ -257,25 +257,25 @@ export default {
|
||||
name: 'item.field.sell_price',
|
||||
fieldType: 'number',
|
||||
},
|
||||
cost_price: {
|
||||
costPrice: {
|
||||
name: 'item.field.cost_price',
|
||||
fieldType: 'number',
|
||||
},
|
||||
costAccount: {
|
||||
costAccountId: {
|
||||
name: 'item.field.cost_account',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
importHint: 'Matches the account name or code.',
|
||||
},
|
||||
sellAccount: {
|
||||
sellAccountId: {
|
||||
name: 'item.field.sell_account',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
importHint: 'Matches the account name or code.',
|
||||
},
|
||||
inventoryAccount: {
|
||||
inventoryAccountId: {
|
||||
name: 'item.field.inventory_account',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Account',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Model } from 'objection';
|
||||
import { castArray, omit, pick } from 'lodash';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
|
||||
@@ -16,7 +17,15 @@ export default class PaginationQueryBuilder extends Model.QueryBuilder {
|
||||
});
|
||||
}
|
||||
|
||||
queryAndThrowIfHasRelations = ({ type, message }) => {
|
||||
queryAndThrowIfHasRelations = ({
|
||||
type,
|
||||
message,
|
||||
excludeRelations = [],
|
||||
includedRelations = [],
|
||||
}) => {
|
||||
const _excludeRelations = castArray(excludeRelations);
|
||||
const _includedRelations = castArray(includedRelations);
|
||||
|
||||
const model = this.modelClass();
|
||||
const modelRelations = Object.keys(model.relationMappings).filter(
|
||||
(relation) =>
|
||||
@@ -25,9 +34,20 @@ export default class PaginationQueryBuilder extends Model.QueryBuilder {
|
||||
) !== -1
|
||||
);
|
||||
const relations = model.secureDeleteRelations || modelRelations;
|
||||
const filteredByIncluded = relations.filter((r) =>
|
||||
_includedRelations.includes(r)
|
||||
);
|
||||
const filteredByExcluded = relations.filter(
|
||||
(r) => !excludeRelations.includes(r)
|
||||
);
|
||||
const filteredRelations = !isEmpty(_includedRelations)
|
||||
? filteredByIncluded
|
||||
: !isEmpty(_excludeRelations)
|
||||
? filteredByExcluded
|
||||
: relations;
|
||||
|
||||
this.runAfter((model, query) => {
|
||||
const nonEmptyRelations = relations.filter(
|
||||
const nonEmptyRelations = filteredRelations.filter(
|
||||
(relation) => !isEmpty(model[relation])
|
||||
);
|
||||
if (nonEmptyRelations.length > 0) {
|
||||
@@ -36,7 +56,7 @@ export default class PaginationQueryBuilder extends Model.QueryBuilder {
|
||||
return model;
|
||||
});
|
||||
return this.onBuild((query) => {
|
||||
relations.forEach((relation) => {
|
||||
filteredRelations.forEach((relation) => {
|
||||
query.withGraphFetched(`${relation}(selectId)`).modifiers({
|
||||
selectId(builder) {
|
||||
builder.select('id');
|
||||
|
||||
@@ -3,7 +3,7 @@ import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import PaymentReceiveSettings from './PaymentReceive.Settings';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceives/constants';
|
||||
import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceived/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class PaymentReceive extends mixin(TenantModel, [
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class PlaidItem extends TenantModel {
|
||||
pausedAt: Date;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
@@ -21,4 +23,19 @@ export default class PlaidItem extends TenantModel {
|
||||
static get relationMappings() {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['isPaused'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the Plaid item feeds syncing is paused.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isPaused() {
|
||||
return !!this.pausedAt;
|
||||
}
|
||||
}
|
||||
|
||||
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 ModelSearchable from './ModelSearchable';
|
||||
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
|
||||
*/
|
||||
@@ -25,6 +30,13 @@ export default class TaxRate extends mixin(TenantModel, [ModelSearchable]) {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the tax rate meta.
|
||||
*/
|
||||
static get meta() {
|
||||
return TaxRateMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/* eslint-disable global-require */
|
||||
import * as R from 'ramda';
|
||||
import { Model, ModelOptions, QueryContext, mixin } from 'objection';
|
||||
import moment from 'moment';
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSettings from './ModelSetting';
|
||||
import Account from './Account';
|
||||
import UncategorizedCashflowTransactionMeta from './UncategorizedCashflowTransaction.meta';
|
||||
|
||||
export default class UncategorizedCashflowTransaction extends mixin(
|
||||
@@ -20,6 +19,8 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
||||
description!: string;
|
||||
plaidTransactionId!: string;
|
||||
recognizedTransactionId!: number;
|
||||
excludedAt: Date;
|
||||
pending: boolean;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
@@ -31,7 +32,7 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
static get timestamps() {
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
@@ -45,6 +46,8 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
||||
'isDepositTransaction',
|
||||
'isWithdrawalTransaction',
|
||||
'isRecognized',
|
||||
'isExcluded',
|
||||
'isPending',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -89,6 +92,22 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
||||
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.
|
||||
*/
|
||||
@@ -133,6 +152,42 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
||||
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);
|
||||
},
|
||||
|
||||
minAmount(query, minAmount) {
|
||||
query.where('amount', '>=', minAmount);
|
||||
},
|
||||
|
||||
maxAmount(query, maxAmount) {
|
||||
query.where('amount', '<=', maxAmount);
|
||||
},
|
||||
|
||||
toDate(query, toDate) {
|
||||
const dateFormat = 'YYYY-MM-DD';
|
||||
const _toDate = moment(toDate).endOf('day').format(dateFormat);
|
||||
|
||||
query.where('date', '<=', _toDate);
|
||||
},
|
||||
|
||||
fromDate(query, fromDate) {
|
||||
const dateFormat = 'YYYY-MM-DD';
|
||||
const _fromDate = moment(fromDate).startOf('day').format(dateFormat);
|
||||
|
||||
query.where('date', '>=', _fromDate);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,12 @@ import { Account } from 'models';
|
||||
import TenantRepository from '@/repositories/TenantRepository';
|
||||
import { IAccount } from '@/interfaces';
|
||||
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 {
|
||||
/**
|
||||
@@ -179,4 +184,67 @@ export default class AccountRepository extends TenantRepository {
|
||||
}
|
||||
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,12 +4,17 @@ import CachableRepository from './CachableRepository';
|
||||
|
||||
export default class TenantRepository extends CachableRepository {
|
||||
repositoryName: string;
|
||||
|
||||
tenantId: number;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {number} tenantId
|
||||
* @param {number} tenantId
|
||||
*/
|
||||
constructor(knex, cache, i18n) {
|
||||
super(knex, cache, i18n);
|
||||
}
|
||||
}
|
||||
|
||||
setTenantId(tenantId: number) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,6 +249,7 @@ export default class Ledger implements ILedger {
|
||||
|
||||
transactionId: entry.referenceId,
|
||||
transactionType: entry.referenceType,
|
||||
transactionSubType: entry.transactionType,
|
||||
|
||||
transactionNumber: entry.transactionNumber,
|
||||
referenceNumber: entry.referenceNumber,
|
||||
@@ -262,6 +263,8 @@ export default class Ledger implements ILedger {
|
||||
|
||||
taxRateId: entry.taxRateId,
|
||||
taxRate: entry.taxRate,
|
||||
|
||||
note: entry.note,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,21 @@ export class AccountTransformer extends Transformer {
|
||||
* @returns {Array}
|
||||
*/
|
||||
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.
|
||||
* @param {IAccount[]}
|
||||
|
||||
@@ -43,8 +43,8 @@ export class AccountsApplication {
|
||||
|
||||
/**
|
||||
* Creates a new account.
|
||||
* @param {number} tenantId
|
||||
* @param {IAccountCreateDTO} accountDTO
|
||||
* @param {number} tenantId
|
||||
* @param {IAccountCreateDTO} accountDTO
|
||||
* @returns {Promise<IAccount>}
|
||||
*/
|
||||
public createAccount = (
|
||||
@@ -108,8 +108,8 @@ export class AccountsApplication {
|
||||
|
||||
/**
|
||||
* Retrieves the account details.
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @returns {Promise<IAccount>}
|
||||
*/
|
||||
public getAccount = (tenantId: number, accountId: number) => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Inject, Service } from 'typedi';
|
||||
import { AccountsApplication } from './AccountsApplication';
|
||||
import { Exportable } from '../Export/Exportable';
|
||||
import { IAccountsFilter, IAccountsStructureType } from '@/interfaces';
|
||||
import { EXPORT_SIZE_LIMIT } from '../Export/constants';
|
||||
|
||||
@Service()
|
||||
export class AccountsExportable extends Exportable {
|
||||
@@ -20,7 +21,7 @@ export class AccountsExportable extends Exportable {
|
||||
inactiveMode: false,
|
||||
...query,
|
||||
structure: IAccountsStructureType.Flat,
|
||||
pageSize: 12000,
|
||||
pageSize: EXPORT_SIZE_LIMIT,
|
||||
page: 1,
|
||||
} as IAccountsFilter;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user